< Summary - Jellyfin

Information
Class: MediaBrowser.Model.Dlna.StreamBuilder
Assembly: MediaBrowser.Model
File(s): /srv/git/jellyfin/MediaBrowser.Model/Dlna/StreamBuilder.cs
Line coverage
67%
Covered lines: 693
Uncovered lines: 332
Coverable lines: 1025
Total lines: 2273
Line coverage: 67.6%
Branch coverage
58%
Covered branches: 551
Total branches: 940
Branch coverage: 58.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
.ctor(...)100%11100%
GetOptimalAudioStream(...)0%7280%
GetOptimalAudioStream(...)0%5550740%
GetOptimalVideoStream(...)87.5%88100%
GetOptimalStream(...)100%11100%
SortMediaSources(...)100%11100%
GetTranscodeReasonForFailedCondition(...)23.07%289.842626.92%
NormalizeMediaSourceFormatIntoSingleContainer(...)100%1414100%
GetAudioDirectPlayProfile(...)0%600240%
GetTranscodeReasonsFromDirectPlayProfile(...)0%420200%
GetDefaultSubtitleStreamIndex(...)65.38%43.212670.58%
SetStreamInfoOptionsFromTranscodingProfile(...)87.5%8.01895.23%
SetStreamInfoOptionsFromDirectPlayProfile(...)0%620%
BuildVideoItem(...)66.34%114.8210490%
GetVideoTranscodeProfile(...)87.5%24.012497.14%
BuildStreamVideoItem(...)89.84%128.4112897.08%
GetDefaultAudioBitrate(...)68.18%33.32271.42%
GetAudioBitrate(...)87.5%32.073295.83%
GetMaxAudioBitrateForTotalBitrate(...)68.75%22.521670.58%
GetVideoDirectPlayProfile(...)84%50.065097.05%
CheckVideoAudioStreamDirectPlay(...)100%22100%
AggregateFailureConditions(...)100%11100%
LogConditionFailure(...)66.66%66100%
GetSubtitleProfile(...)14.28%496.664236.36%
IsSubtitleEmbedSupported(...)0%4260%
GetExternalSubtitleProfile(...)91.66%3636100%
IsBitrateLimitExceeded(...)66.66%6.03690.9%
ValidateMediaOptions(...)68.75%34.521658.33%
GetProfileConditionsForVideoAudio(...)100%11100%
GetProfileConditionsForAudio(...)0%620%
ApplyTranscodingConditions(...)61.36%8369.5722044.78%
IsAudioDirectPlaySupported(...)0%4260%
IsAudioDirectStreamSupported(...)0%7280%
GetRank(...)100%44100%

File(s)

/srv/git/jellyfin/MediaBrowser.Model/Dlna/StreamBuilder.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Globalization;
 4using System.Linq;
 5using Jellyfin.Data.Enums;
 6using Jellyfin.Extensions;
 7using MediaBrowser.Model.Dto;
 8using MediaBrowser.Model.Entities;
 9using MediaBrowser.Model.Extensions;
 10using MediaBrowser.Model.MediaInfo;
 11using MediaBrowser.Model.Session;
 12using Microsoft.Extensions.Logging;
 13
 14namespace MediaBrowser.Model.Dlna
 15{
 16    /// <summary>
 17    /// Class StreamBuilder.
 18    /// </summary>
 19    public class StreamBuilder
 20    {
 21        // Aliases
 22        internal const TranscodeReason ContainerReasons = TranscodeReason.ContainerNotSupported | TranscodeReason.Contai
 23        internal const TranscodeReason AudioCodecReasons = TranscodeReason.AudioBitrateNotSupported | TranscodeReason.Au
 24        internal const TranscodeReason AudioReasons = TranscodeReason.AudioCodecNotSupported | AudioCodecReasons;
 25        internal const TranscodeReason VideoCodecReasons = TranscodeReason.VideoResolutionNotSupported | TranscodeReason
 26        internal const TranscodeReason VideoReasons = TranscodeReason.VideoCodecNotSupported | VideoCodecReasons;
 27        internal const TranscodeReason DirectStreamReasons = AudioReasons | TranscodeReason.ContainerNotSupported | Tran
 28
 29        private readonly ILogger _logger;
 30        private readonly ITranscoderSupport _transcoderSupport;
 131        private static readonly string[] _supportedHlsVideoCodecs = ["h264", "hevc", "vp9", "av1"];
 132        private static readonly string[] _supportedHlsAudioCodecsTs = ["aac", "ac3", "eac3", "mp3"];
 133        private static readonly string[] _supportedHlsAudioCodecsMp4 = ["aac", "ac3", "eac3", "mp3", "alac", "flac", "op
 34
 35        /// <summary>
 36        /// Initializes a new instance of the <see cref="StreamBuilder"/> class.
 37        /// </summary>
 38        /// <param name="transcoderSupport">The <see cref="ITranscoderSupport"/> object.</param>
 39        /// <param name="logger">The <see cref="ILogger"/> object.</param>
 40        public StreamBuilder(ITranscoderSupport transcoderSupport, ILogger logger)
 41        {
 26442            _transcoderSupport = transcoderSupport;
 26443            _logger = logger;
 26444        }
 45
 46        /// <summary>
 47        /// Gets the optimal audio stream.
 48        /// </summary>
 49        /// <param name="options">The <see cref="MediaOptions"/> object to get the audio stream from.</param>
 50        /// <returns>The <see cref="StreamInfo"/> of the optimal audio stream.</returns>
 51        public StreamInfo? GetOptimalAudioStream(MediaOptions options)
 52        {
 053            ValidateMediaOptions(options, false);
 54
 055            List<StreamInfo> streams = [];
 056            foreach (var mediaSource in options.MediaSources)
 57            {
 058                if (!(string.IsNullOrEmpty(options.MediaSourceId)
 059                    || string.Equals(mediaSource.Id, options.MediaSourceId, StringComparison.OrdinalIgnoreCase)))
 60                {
 61                    continue;
 62                }
 63
 064                StreamInfo? streamInfo = GetOptimalAudioStream(mediaSource, options);
 065                if (streamInfo is not null)
 66                {
 067                    streamInfo.DeviceId = options.DeviceId;
 068                    streamInfo.DeviceProfileId = options.Profile.Id.ToString("N", CultureInfo.InvariantCulture);
 069                    streams.Add(streamInfo);
 70                }
 71            }
 72
 073            return GetOptimalStream(streams, options.GetMaxBitrate(true) ?? 0);
 74        }
 75
 76        private StreamInfo? GetOptimalAudioStream(MediaSourceInfo item, MediaOptions options)
 77        {
 078            var playlistItem = new StreamInfo
 079            {
 080                ItemId = options.ItemId,
 081                MediaType = DlnaProfileType.Audio,
 082                MediaSource = item,
 083                RunTimeTicks = item.RunTimeTicks,
 084                Context = options.Context,
 085                DeviceProfile = options.Profile
 086            };
 87
 088            if (options.ForceDirectPlay)
 89            {
 090                playlistItem.PlayMethod = PlayMethod.DirectPlay;
 091                playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, 
 092                return playlistItem;
 93            }
 94
 095            if (options.ForceDirectStream)
 96            {
 097                playlistItem.PlayMethod = PlayMethod.DirectStream;
 098                playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, 
 099                return playlistItem;
 100            }
 101
 0102            MediaStream audioStream = item.GetDefaultAudioStream(null);
 103
 0104            var directPlayInfo = GetAudioDirectPlayProfile(item, audioStream, options);
 105
 0106            var directPlayMethod = directPlayInfo.PlayMethod;
 0107            var transcodeReasons = directPlayInfo.TranscodeReasons;
 108
 0109            var inputAudioChannels = audioStream?.Channels;
 0110            var inputAudioBitrate = audioStream?.BitRate;
 0111            var inputAudioSampleRate = audioStream?.SampleRate;
 0112            var inputAudioBitDepth = audioStream?.BitDepth;
 113
 0114            if (directPlayMethod is PlayMethod.DirectPlay)
 115            {
 0116                var profile = options.Profile;
 0117                var audioFailureConditions = GetProfileConditionsForAudio(profile.CodecProfiles, item.Container, audioSt
 0118                var audioFailureReasons = AggregateFailureConditions(item, profile, "AudioCodecProfile", audioFailureCon
 0119                transcodeReasons |= audioFailureReasons;
 120
 0121                if (audioFailureReasons == 0)
 122                {
 0123                    playlistItem.PlayMethod = directPlayMethod.Value;
 0124                    playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profi
 125
 0126                    return playlistItem;
 127                }
 128            }
 129
 0130            if (directPlayMethod is PlayMethod.DirectStream)
 131            {
 0132                var remuxContainer = item.TranscodingContainer ?? "ts";
 0133                string[] supportedHlsContainers = ["ts", "mp4"];
 134                // If the container specified for the profile is an HLS supported container, use that container instead,
 135                // The client should be responsible to ensure this container is compatible
 0136                remuxContainer = Array.Exists(supportedHlsContainers, element => string.Equals(element, directPlayInfo.P
 137                bool codeIsSupported;
 0138                if (item.TranscodingSubProtocol == MediaStreamProtocol.hls)
 139                {
 140                    // Enforce HLS audio codec restrictions
 0141                    if (string.Equals(remuxContainer, "mp4", StringComparison.OrdinalIgnoreCase))
 142                    {
 0143                        codeIsSupported = _supportedHlsAudioCodecsMp4.Contains(directPlayInfo.Profile?.AudioCodec ?? dir
 144                    }
 145                    else
 146                    {
 0147                        codeIsSupported = _supportedHlsAudioCodecsTs.Contains(directPlayInfo.Profile?.AudioCodec ?? dire
 148                    }
 149                }
 150                else
 151                {
 152                    // Let's assume the client has given a correct container for http
 0153                    codeIsSupported = true;
 154                }
 155
 0156                if (codeIsSupported)
 157                {
 0158                    playlistItem.PlayMethod = directPlayMethod.Value;
 0159                    playlistItem.Container = remuxContainer;
 0160                    playlistItem.TranscodeReasons = transcodeReasons;
 0161                    playlistItem.SubProtocol = item.TranscodingSubProtocol;
 0162                    item.TranscodingContainer = remuxContainer;
 0163                    return playlistItem;
 164                }
 165
 0166                transcodeReasons |= TranscodeReason.AudioCodecNotSupported;
 0167                playlistItem.TranscodeReasons = transcodeReasons;
 168            }
 169
 0170            TranscodingProfile? transcodingProfile = null;
 0171            foreach (var tcProfile in options.Profile.TranscodingProfiles)
 172            {
 0173                if (tcProfile.Type == playlistItem.MediaType
 0174                    && tcProfile.Context == options.Context
 0175                    && _transcoderSupport.CanEncodeToAudioCodec(tcProfile.AudioCodec ?? tcProfile.Container))
 176                {
 0177                    transcodingProfile = tcProfile;
 0178                    break;
 179                }
 180            }
 181
 0182            if (transcodingProfile is not null)
 183            {
 0184                if (!item.SupportsTranscoding)
 185                {
 0186                    return null;
 187                }
 188
 0189                SetStreamInfoOptionsFromTranscodingProfile(item, playlistItem, transcodingProfile);
 190
 0191                var audioTranscodingConditions = GetProfileConditionsForAudio(options.Profile.CodecProfiles, transcoding
 0192                ApplyTranscodingConditions(playlistItem, audioTranscodingConditions, null, true, true);
 193
 194                // Honor requested max channels
 0195                playlistItem.GlobalMaxAudioChannels = options.MaxAudioChannels;
 196
 0197                var configuredBitrate = options.GetMaxBitrate(true);
 198
 0199                long transcodingBitrate = options.AudioTranscodingBitrate
 0200                    ?? (options.Context == EncodingContext.Streaming ? options.Profile.MusicStreamingTranscodingBitrate 
 0201                    ?? configuredBitrate
 0202                    ?? 128000;
 203
 0204                if (configuredBitrate.HasValue)
 205                {
 0206                    transcodingBitrate = Math.Min(configuredBitrate.Value, transcodingBitrate);
 207                }
 208
 0209                var longBitrate = Math.Min(transcodingBitrate, playlistItem.AudioBitrate ?? transcodingBitrate);
 0210                playlistItem.AudioBitrate = longBitrate > int.MaxValue ? int.MaxValue : Convert.ToInt32(longBitrate);
 211            }
 212
 0213            playlistItem.TranscodeReasons = transcodeReasons;
 0214            return playlistItem;
 215        }
 216
 217        /// <summary>
 218        /// Gets the optimal video stream.
 219        /// </summary>
 220        /// <param name="options">The <see cref="MediaOptions"/> object to get the video stream from.</param>
 221        /// <returns>The <see cref="StreamInfo"/> of the optimal video stream.</returns>
 222        public StreamInfo? GetOptimalVideoStream(MediaOptions options)
 223        {
 264224            ValidateMediaOptions(options, true);
 225
 264226            var mediaSources = string.IsNullOrEmpty(options.MediaSourceId)
 264227                ? options.MediaSources
 264228                : options.MediaSources.Where(x => string.Equals(x.Id, options.MediaSourceId, StringComparison.OrdinalIgn
 229
 264230            List<StreamInfo> streams = [];
 1056231            foreach (var mediaSourceInfo in mediaSources)
 232            {
 264233                var streamInfo = BuildVideoItem(mediaSourceInfo, options);
 264234                if (streamInfo is not null)
 235                {
 264236                    streams.Add(streamInfo);
 237                }
 238            }
 239
 1056240            foreach (var stream in streams)
 241            {
 264242                stream.DeviceId = options.DeviceId;
 264243                stream.DeviceProfileId = options.Profile.Id.ToString("N", CultureInfo.InvariantCulture);
 244            }
 245
 264246            return GetOptimalStream(streams, options.GetMaxBitrate(false) ?? 0);
 247        }
 248
 249        private static StreamInfo? GetOptimalStream(List<StreamInfo> streams, long maxBitrate)
 264250            => SortMediaSources(streams, maxBitrate).FirstOrDefault();
 251
 252        private static IOrderedEnumerable<StreamInfo> SortMediaSources(List<StreamInfo> streams, long maxBitrate)
 253        {
 264254            return streams.OrderBy(i =>
 264255            {
 264256                // Nothing beats direct playing a file
 264257                if (i.PlayMethod == PlayMethod.DirectPlay && i.MediaSource?.Protocol == MediaProtocol.File)
 264258                {
 264259                    return 0;
 264260                }
 264261
 264262                return 1;
 264263            }).ThenBy(i =>
 264264            {
 264265                switch (i.PlayMethod)
 264266                {
 264267                    // Let's assume direct streaming a file is just as desirable as direct playing a remote url
 264268                    case PlayMethod.DirectStream:
 264269                    case PlayMethod.DirectPlay:
 264270                        return 0;
 264271                    default:
 264272                        return 1;
 264273                }
 264274            }).ThenBy(i =>
 264275            {
 264276                switch (i.MediaSource?.Protocol)
 264277                {
 264278                    case MediaProtocol.File:
 264279                        return 0;
 264280                    default:
 264281                        return 1;
 264282                }
 264283            }).ThenBy(i =>
 264284            {
 264285                if (maxBitrate > 0)
 264286                {
 264287                    if (i.MediaSource?.Bitrate is not null)
 264288                    {
 264289                        return Math.Abs(i.MediaSource.Bitrate.Value - maxBitrate);
 264290                    }
 264291                }
 264292
 264293                return 0;
 264294            }).ThenBy(streams.IndexOf);
 295        }
 296
 297        private static TranscodeReason GetTranscodeReasonForFailedCondition(ProfileCondition condition)
 298        {
 107299            switch (condition.Property)
 300            {
 301                case ProfileConditionValue.AudioBitrate:
 0302                    return TranscodeReason.AudioBitrateNotSupported;
 303
 304                case ProfileConditionValue.AudioChannels:
 17305                    return TranscodeReason.AudioChannelsNotSupported;
 306
 307                case ProfileConditionValue.AudioProfile:
 0308                    return TranscodeReason.AudioProfileNotSupported;
 309
 310                case ProfileConditionValue.AudioSampleRate:
 0311                    return TranscodeReason.AudioSampleRateNotSupported;
 312
 313                case ProfileConditionValue.Has64BitOffsets:
 314                    // TODO
 0315                    return 0;
 316
 317                case ProfileConditionValue.Height:
 0318                    return TranscodeReason.VideoResolutionNotSupported;
 319
 320                case ProfileConditionValue.IsAnamorphic:
 0321                    return TranscodeReason.AnamorphicVideoNotSupported;
 322
 323                case ProfileConditionValue.IsAvc:
 324                    // TODO
 0325                    return 0;
 326
 327                case ProfileConditionValue.IsInterlaced:
 0328                    return TranscodeReason.InterlacedVideoNotSupported;
 329
 330                case ProfileConditionValue.IsSecondaryAudio:
 35331                    return TranscodeReason.SecondaryAudioNotSupported;
 332
 333                case ProfileConditionValue.NumAudioStreams:
 334                    // TODO
 0335                    return 0;
 336
 337                case ProfileConditionValue.NumVideoStreams:
 338                    // TODO
 0339                    return 0;
 340
 341                case ProfileConditionValue.PacketLength:
 342                    // TODO
 0343                    return 0;
 344
 345                case ProfileConditionValue.RefFrames:
 0346                    return TranscodeReason.RefFramesNotSupported;
 347
 348                case ProfileConditionValue.VideoBitDepth:
 0349                    return TranscodeReason.VideoBitDepthNotSupported;
 350
 351                case ProfileConditionValue.AudioBitDepth:
 0352                    return TranscodeReason.AudioBitDepthNotSupported;
 353
 354                case ProfileConditionValue.VideoBitrate:
 0355                    return TranscodeReason.VideoBitrateNotSupported;
 356
 357                case ProfileConditionValue.VideoCodecTag:
 9358                    return TranscodeReason.VideoCodecTagNotSupported;
 359
 360                case ProfileConditionValue.VideoFramerate:
 0361                    return TranscodeReason.VideoFramerateNotSupported;
 362
 363                case ProfileConditionValue.VideoLevel:
 13364                    return TranscodeReason.VideoLevelNotSupported;
 365
 366                case ProfileConditionValue.VideoProfile:
 17367                    return TranscodeReason.VideoProfileNotSupported;
 368
 369                case ProfileConditionValue.VideoRangeType:
 16370                    return TranscodeReason.VideoRangeTypeNotSupported;
 371
 372                case ProfileConditionValue.VideoTimestamp:
 373                    // TODO
 0374                    return 0;
 375
 376                case ProfileConditionValue.Width:
 0377                    return TranscodeReason.VideoResolutionNotSupported;
 378
 379                default:
 0380                    return 0;
 381            }
 382        }
 383
 384        /// <summary>
 385        /// Normalizes input container.
 386        /// </summary>
 387        /// <param name="inputContainer">The input container.</param>
 388        /// <param name="profile">The <see cref="DeviceProfile"/>.</param>
 389        /// <param name="type">The <see cref="DlnaProfileType"/>.</param>
 390        /// <param name="playProfile">The <see cref="DirectPlayProfile"/> object to get the video stream from.</param>
 391        /// <returns>The normalized input container.</returns>
 392        public static string NormalizeMediaSourceFormatIntoSingleContainer(string inputContainer, DeviceProfile? profile
 393        {
 380394            if (profile is null || !inputContainer.Contains(',', StringComparison.OrdinalIgnoreCase))
 395            {
 82396                return inputContainer;
 397            }
 398
 298399            var formats = ContainerHelper.Split(inputContainer);
 298400            var playProfiles = playProfile is null ? profile.DirectPlayProfiles : [playProfile];
 1315401            foreach (var format in formats)
 402            {
 2495403                foreach (var directPlayProfile in playProfiles)
 404                {
 888405                    if (directPlayProfile.Type != type)
 406                    {
 407                        continue;
 408                    }
 409
 660410                    if (directPlayProfile.SupportsContainer(format))
 411                    {
 281412                        return format;
 413                    }
 414                }
 415            }
 416
 17417            return inputContainer;
 418        }
 419
 420        private (DirectPlayProfile? Profile, PlayMethod? PlayMethod, TranscodeReason TranscodeReasons) GetAudioDirectPla
 421        {
 0422            var directPlayProfile = options.Profile.DirectPlayProfiles
 0423                .FirstOrDefault(x => x.Type == DlnaProfileType.Audio && IsAudioDirectPlaySupported(x, item, audioStream)
 424
 0425            TranscodeReason transcodeReasons = 0;
 0426            if (directPlayProfile is null)
 427            {
 0428                _logger.LogDebug(
 0429                    "Profile: {0}, No audio direct play profiles found for {1} with codec {2}",
 0430                    options.Profile.Name ?? "Unknown Profile",
 0431                    item.Path ?? "Unknown path",
 0432                    audioStream.Codec ?? "Unknown codec");
 433
 0434                var directStreamProfile = options.Profile.DirectPlayProfiles
 0435                    .FirstOrDefault(x => x.Type == DlnaProfileType.Audio && IsAudioDirectStreamSupported(x, item, audioS
 436
 0437                if (directStreamProfile is not null)
 438                {
 0439                    directPlayProfile = directStreamProfile;
 0440                    transcodeReasons |= TranscodeReason.ContainerNotSupported;
 441                }
 442                else
 443                {
 0444                    return (null, null, GetTranscodeReasonsFromDirectPlayProfile(item, null, audioStream, options.Profil
 445                }
 446            }
 447
 448            // The profile describes what the device supports
 449            // If device requirements are satisfied then allow both direct stream and direct play
 450            // Note: As of 10.10 codebase, SupportsDirectPlay is always true because the MediaSourceInfo initializes thi
 451            // Need to check additionally for current transcode reasons
 0452            if (item.SupportsDirectPlay && transcodeReasons == 0)
 453            {
 0454                if (!IsBitrateLimitExceeded(item, options.GetMaxBitrate(true) ?? 0))
 455                {
 0456                    if (options.EnableDirectPlay)
 457                    {
 0458                        return (directPlayProfile, PlayMethod.DirectPlay, 0);
 459                    }
 460                }
 461                else
 462                {
 0463                    transcodeReasons |= TranscodeReason.ContainerBitrateExceedsLimit;
 464                }
 465            }
 466
 467            // While options takes the network and other factors into account. Only applies to direct stream
 0468            if (item.SupportsDirectStream)
 469            {
 0470                if (!IsBitrateLimitExceeded(item, options.GetMaxBitrate(true) ?? 0))
 471                {
 472                    // Note: as of 10.10 codebase, the options.EnableDirectStream is always false due to
 473                    // "direct-stream http streaming is currently broken"
 474                    // Don't check that option for audio as we always assume that is supported
 0475                    if (transcodeReasons == TranscodeReason.ContainerNotSupported)
 476                    {
 0477                        return (directPlayProfile, PlayMethod.DirectStream, transcodeReasons);
 478                    }
 479                }
 480                else
 481                {
 0482                    transcodeReasons |= TranscodeReason.ContainerBitrateExceedsLimit;
 483                }
 484            }
 485
 0486            return (directPlayProfile, null, transcodeReasons);
 487        }
 488
 489        private static TranscodeReason GetTranscodeReasonsFromDirectPlayProfile(MediaSourceInfo item, MediaStream? video
 490        {
 0491            var mediaType = videoStream is null ? DlnaProfileType.Audio : DlnaProfileType.Video;
 492
 0493            var containerSupported = false;
 0494            var audioSupported = false;
 0495            var videoSupported = false;
 496
 0497            foreach (var profile in directPlayProfiles)
 498            {
 499                // Check container type
 0500                if (profile.Type == mediaType && profile.SupportsContainer(item.Container))
 501                {
 0502                    containerSupported = true;
 503
 0504                    videoSupported = videoStream is null || profile.SupportsVideoCodec(videoStream.Codec);
 505
 0506                    audioSupported = audioStream is null || profile.SupportsAudioCodec(audioStream.Codec);
 507
 0508                    if (videoSupported && audioSupported)
 509                    {
 0510                        break;
 511                    }
 512                }
 513            }
 514
 0515            TranscodeReason reasons = 0;
 0516            if (!containerSupported)
 517            {
 0518                reasons |= TranscodeReason.ContainerNotSupported;
 519            }
 520
 0521            if (!videoSupported)
 522            {
 0523                reasons |= TranscodeReason.VideoCodecNotSupported;
 524            }
 525
 0526            if (!audioSupported)
 527            {
 0528                reasons |= TranscodeReason.AudioCodecNotSupported;
 529            }
 530
 0531            return reasons;
 532        }
 533
 534        private static int? GetDefaultSubtitleStreamIndex(MediaSourceInfo item, SubtitleProfile[] subtitleProfiles)
 535        {
 161536            int highestScore = -1;
 1338537            foreach (var stream in item.MediaStreams)
 538            {
 508539                if (stream.Type == MediaStreamType.Subtitle
 508540                    && stream.Score.HasValue
 508541                    && stream.Score.Value > highestScore)
 542                {
 138543                    highestScore = stream.Score.Value;
 544                }
 545            }
 546
 161547            List<MediaStream> topStreams = [];
 1338548            foreach (var stream in item.MediaStreams)
 549            {
 508550                if (stream.Type == MediaStreamType.Subtitle && stream.Score.HasValue && stream.Score.Value == highestSco
 551                {
 138552                    topStreams.Add(stream);
 553                }
 554            }
 555
 556            // If multiple streams have an equal score, try to pick the most efficient one
 161557            if (topStreams.Count > 1)
 558            {
 0559                foreach (var stream in topStreams)
 560                {
 0561                    foreach (var profile in subtitleProfiles)
 562                    {
 0563                        if (profile.Method == SubtitleDeliveryMethod.External && string.Equals(profile.Format, stream.Co
 564                        {
 0565                            return stream.Index;
 566                        }
 567                    }
 568                }
 569            }
 570
 571            // If no optimization panned out, just use the original default
 161572            return item.DefaultSubtitleStreamIndex;
 0573        }
 574
 575        private static void SetStreamInfoOptionsFromTranscodingProfile(MediaSourceInfo item, StreamInfo playlistItem, Tr
 576        {
 140577            var container = transcodingProfile.Container;
 140578            var protocol = transcodingProfile.Protocol;
 579
 140580            item.TranscodingContainer = container;
 140581            item.TranscodingSubProtocol = protocol;
 582
 140583            if (playlistItem.PlayMethod == PlayMethod.Transcode)
 584            {
 140585                playlistItem.Container = container;
 140586                playlistItem.SubProtocol = protocol;
 587            }
 588
 140589            playlistItem.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo;
 140590            if (int.TryParse(transcodingProfile.MaxAudioChannels, CultureInfo.InvariantCulture, out int transcodingMaxAu
 591            {
 127592                playlistItem.TranscodingMaxAudioChannels = transcodingMaxAudioChannels;
 593            }
 594
 140595            playlistItem.EstimateContentLength = transcodingProfile.EstimateContentLength;
 596
 140597            playlistItem.CopyTimestamps = transcodingProfile.CopyTimestamps;
 140598            playlistItem.EnableSubtitlesInManifest = transcodingProfile.EnableSubtitlesInManifest;
 140599            playlistItem.EnableMpegtsM2TsMode = transcodingProfile.EnableMpegtsM2TsMode;
 600
 140601            playlistItem.BreakOnNonKeyFrames = transcodingProfile.BreakOnNonKeyFrames;
 140602            playlistItem.EnableAudioVbrEncoding = transcodingProfile.EnableAudioVbrEncoding;
 603
 140604            if (transcodingProfile.MinSegments > 0)
 605            {
 96606                playlistItem.MinSegments = transcodingProfile.MinSegments;
 607            }
 608
 140609            if (transcodingProfile.SegmentLength > 0)
 610            {
 0611                playlistItem.SegmentLength = transcodingProfile.SegmentLength;
 612            }
 140613        }
 614
 615        private static void SetStreamInfoOptionsFromDirectPlayProfile(MediaOptions options, MediaSourceInfo item, Stream
 616        {
 0617            var container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileTy
 0618            var protocol = MediaStreamProtocol.http;
 619
 0620            item.TranscodingContainer = container;
 0621            item.TranscodingSubProtocol = protocol;
 622
 0623            playlistItem.Container = container;
 0624            playlistItem.SubProtocol = protocol;
 625
 0626            playlistItem.VideoCodecs = [item.VideoStream.Codec];
 0627            playlistItem.AudioCodecs = ContainerHelper.Split(directPlayProfile?.AudioCodec);
 0628        }
 629
 630        private StreamInfo BuildVideoItem(MediaSourceInfo item, MediaOptions options)
 631        {
 264632            ArgumentNullException.ThrowIfNull(item);
 633
 264634            StreamInfo playlistItem = new StreamInfo
 264635            {
 264636                ItemId = options.ItemId,
 264637                MediaType = DlnaProfileType.Video,
 264638                MediaSource = item,
 264639                RunTimeTicks = item.RunTimeTicks,
 264640                Context = options.Context,
 264641                DeviceProfile = options.Profile,
 264642                SubtitleStreamIndex = options.SubtitleStreamIndex ?? GetDefaultSubtitleStreamIndex(item, options.Profile
 264643            };
 644
 264645            var subtitleStream = playlistItem.SubtitleStreamIndex.HasValue ? item.GetMediaStream(MediaStreamType.Subtitl
 646
 264647            var audioStream = item.GetDefaultAudioStream(options.AudioStreamIndex ?? item.DefaultAudioStreamIndex);
 264648            if (audioStream is not null)
 649            {
 263650                playlistItem.AudioStreamIndex = audioStream.Index;
 651            }
 652
 653            // Collect candidate audio streams
 264654            ICollection<MediaStream> candidateAudioStreams = audioStream is null ? [] : [audioStream];
 264655            if (!options.AudioStreamIndex.HasValue || options.AudioStreamIndex < 0)
 656            {
 161657                if (audioStream?.IsDefault == true)
 658                {
 160659                    candidateAudioStreams = item.MediaStreams.Where(stream => stream.Type == MediaStreamType.Audio && st
 660                }
 661                else
 662                {
 1663                    candidateAudioStreams = item.MediaStreams.Where(stream => stream.Type == MediaStreamType.Audio && st
 664                }
 665            }
 666
 264667            var videoStream = item.VideoStream;
 668
 264669            var bitrateLimitExceeded = IsBitrateLimitExceeded(item, options.GetMaxBitrate(false) ?? 0);
 264670            var isEligibleForDirectPlay = options.EnableDirectPlay && (options.ForceDirectPlay || !bitrateLimitExceeded)
 264671            var isEligibleForDirectStream = options.EnableDirectStream && (options.ForceDirectStream || !bitrateLimitExc
 264672            TranscodeReason transcodeReasons = 0;
 673
 674            // Force transcode or remux for BD/DVD folders
 264675            if (item.VideoType == VideoType.Dvd || item.VideoType == VideoType.BluRay)
 676            {
 0677                isEligibleForDirectPlay = false;
 678            }
 679
 264680            if (bitrateLimitExceeded)
 681            {
 24682                transcodeReasons = TranscodeReason.ContainerBitrateExceedsLimit;
 683            }
 684
 264685            _logger.LogDebug(
 264686                "Profile: {0}, Path: {1}, isEligibleForDirectPlay: {2}, isEligibleForDirectStream: {3}",
 264687                options.Profile.Name ?? "Unknown Profile",
 264688                item.Path ?? "Unknown path",
 264689                isEligibleForDirectPlay,
 264690                isEligibleForDirectStream);
 691
 264692            DirectPlayProfile? directPlayProfile = null;
 264693            if (isEligibleForDirectPlay || isEligibleForDirectStream)
 694            {
 695                // See if it can be direct played
 240696                var directPlayInfo = GetVideoDirectPlayProfile(options, item, videoStream, audioStream, candidateAudioSt
 240697                var directPlay = directPlayInfo.PlayMethod;
 240698                transcodeReasons |= directPlayInfo.TranscodeReasons;
 699
 240700                if (directPlay.HasValue)
 701                {
 116702                    directPlayProfile = directPlayInfo.Profile;
 116703                    playlistItem.PlayMethod = directPlay.Value;
 116704                    playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profi
 116705                    var videoCodec = videoStream?.Codec;
 116706                    playlistItem.VideoCodecs = videoCodec is null ? [] : [videoCodec];
 707
 116708                    if (directPlay == PlayMethod.DirectPlay)
 709                    {
 116710                        playlistItem.SubProtocol = MediaStreamProtocol.http;
 711
 116712                        var audioStreamIndex = directPlayInfo.AudioStreamIndex ?? audioStream?.Index;
 116713                        if (audioStreamIndex.HasValue)
 714                        {
 116715                            playlistItem.AudioStreamIndex = audioStreamIndex;
 116716                            var audioCodec = item.GetMediaStream(MediaStreamType.Audio, audioStreamIndex.Value)?.Codec;
 116717                            playlistItem.AudioCodecs = audioCodec is null ? [] : [audioCodec];
 718                        }
 719                    }
 0720                    else if (directPlay == PlayMethod.DirectStream)
 721                    {
 0722                        playlistItem.AudioStreamIndex = audioStream?.Index;
 0723                        if (audioStream is not null)
 724                        {
 0725                            playlistItem.AudioCodecs = ContainerHelper.Split(directPlayProfile?.AudioCodec);
 726                        }
 727
 0728                        SetStreamInfoOptionsFromDirectPlayProfile(options, item, playlistItem, directPlayProfile);
 0729                        BuildStreamVideoItem(playlistItem, options, item, videoStream, audioStream, candidateAudioStream
 730                    }
 731
 116732                    if (subtitleStream is not null)
 733                    {
 112734                        var subtitleProfile = GetSubtitleProfile(item, subtitleStream, options.Profile.SubtitleProfiles,
 735
 112736                        playlistItem.SubtitleDeliveryMethod = subtitleProfile.Method;
 112737                        playlistItem.SubtitleFormat = subtitleProfile.Format;
 738                    }
 739                }
 740
 240741                _logger.LogDebug(
 240742                    "DirectPlay Result for Profile: {0}, Path: {1}, PlayMethod: {2}, AudioStreamIndex: {3}, SubtitleStre
 240743                    options.Profile.Name ?? "Anonymous Profile",
 240744                    item.Path ?? "Unknown path",
 240745                    directPlayInfo.PlayMethod,
 240746                    directPlayInfo.AudioStreamIndex ?? audioStream?.Index,
 240747                    playlistItem.SubtitleStreamIndex,
 240748                    directPlayInfo.TranscodeReasons);
 749            }
 750
 264751            playlistItem.TranscodeReasons = transcodeReasons;
 752
 264753            if (playlistItem.PlayMethod != PlayMethod.DirectStream && playlistItem.PlayMethod != PlayMethod.DirectPlay)
 754            {
 755                // Can't direct play, find the transcoding profile
 756                // If we do this for direct-stream we will overwrite the info
 148757                var (transcodingProfile, playMethod) = GetVideoTranscodeProfile(item, options, videoStream, audioStream,
 758
 148759                if (transcodingProfile is not null && playMethod.HasValue)
 760                {
 140761                    SetStreamInfoOptionsFromTranscodingProfile(item, playlistItem, transcodingProfile);
 762
 140763                    BuildStreamVideoItem(playlistItem, options, item, videoStream, audioStream, candidateAudioStreams, t
 764
 140765                    playlistItem.PlayMethod = PlayMethod.Transcode;
 766
 140767                    if (subtitleStream is not null)
 768                    {
 121769                        var subtitleProfile = GetSubtitleProfile(item, subtitleStream, options.Profile.SubtitleProfiles,
 770
 121771                        if (options.AlwaysBurnInSubtitleWhenTranscoding && (playlistItem.TranscodeReasons & (VideoReason
 772                        {
 0773                            playlistItem.SubtitleDeliveryMethod = SubtitleDeliveryMethod.Encode;
 0774                            foreach (SubtitleProfile profile in options.Profile.SubtitleProfiles)
 775                            {
 0776                                profile.Method = SubtitleDeliveryMethod.Encode;
 777                            }
 778                        }
 779                        else
 780                        {
 121781                            playlistItem.SubtitleDeliveryMethod = subtitleProfile.Method;
 782                        }
 783
 121784                        playlistItem.SubtitleFormat = subtitleProfile.Format;
 121785                        playlistItem.SubtitleCodecs = [subtitleProfile.Format];
 786                    }
 787
 140788                    if ((playlistItem.TranscodeReasons & (VideoReasons | TranscodeReason.ContainerBitrateExceedsLimit)) 
 789                    {
 40790                        ApplyTranscodingConditions(playlistItem, transcodingProfile.Conditions, null, true, true);
 791                    }
 792                }
 793            }
 794
 264795            _logger.LogDebug(
 264796                "StreamBuilder.BuildVideoItem( Profile={0}, Path={1}, AudioStreamIndex={2}, SubtitleStreamIndex={3} ) =>
 264797                options.Profile.Name ?? "Anonymous Profile",
 264798                item.Path ?? "Unknown path",
 264799                options.AudioStreamIndex,
 264800                options.SubtitleStreamIndex,
 264801                playlistItem.PlayMethod,
 264802                playlistItem.TranscodeReasons,
 264803                playlistItem.ToUrl("media:", "<token>"));
 804
 264805            item.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileT
 264806            return playlistItem;
 807        }
 808
 809        private (TranscodingProfile? Profile, PlayMethod? PlayMethod) GetVideoTranscodeProfile(
 810            MediaSourceInfo item,
 811            MediaOptions options,
 812            MediaStream? videoStream,
 813            MediaStream? audioStream,
 814            StreamInfo playlistItem)
 815        {
 148816            if (!(item.SupportsTranscoding || item.SupportsDirectStream))
 817            {
 0818                return (null, null);
 819            }
 820
 148821            var transcodingProfiles = options.Profile.TranscodingProfiles
 148822                .Where(i => i.Type == playlistItem.MediaType && i.Context == options.Context);
 823
 148824            if (item.UseMostCompatibleTranscodingProfile)
 825            {
 0826                transcodingProfiles = transcodingProfiles.Where(i => string.Equals(i.Container, "ts", StringComparison.O
 827            }
 828
 148829            var videoCodec = videoStream?.Codec;
 148830            float videoFramerate = videoStream?.ReferenceFrameRate ?? 0;
 148831            TransportStreamTimestamp? timestamp = videoStream is null ? TransportStreamTimestamp.None : item.Timestamp;
 148832            int? numAudioStreams = item.GetStreamCount(MediaStreamType.Audio);
 148833            int? numVideoStreams = item.GetStreamCount(MediaStreamType.Video);
 834
 148835            var audioCodec = audioStream?.Codec;
 148836            var audioProfile = audioStream?.Profile;
 148837            var audioChannels = audioStream?.Channels;
 148838            var audioBitrate = audioStream?.BitRate;
 148839            var audioSampleRate = audioStream?.SampleRate;
 148840            var audioBitDepth = audioStream?.BitDepth;
 841
 148842            var analyzedProfiles = transcodingProfiles
 148843                .Select(transcodingProfile =>
 148844                {
 148845                    var rank = (Video: 3, Audio: 3);
 148846
 148847                    var container = transcodingProfile.Container;
 148848
 148849                    if (options.AllowVideoStreamCopy)
 148850                    {
 148851                        if (ContainerHelper.ContainsContainer(transcodingProfile.VideoCodec, videoCodec))
 148852                        {
 148853                            var appliedVideoConditions = options.Profile.CodecProfiles
 148854                                .Where(i => i.Type == CodecType.Video &&
 148855                                    i.ContainsAnyCodec(videoCodec, container) &&
 148856                                    i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoConditionSatisfied
 148857                                .Select(i =>
 148858                                    i.Conditions.All(condition => ConditionProcessor.IsVideoConditionSatisfied(condition
 148859
 148860                            // An empty appliedVideoConditions means that the codec has no conditions for the current vi
 148861                            var conditionsSatisfied = appliedVideoConditions.All(satisfied => satisfied);
 148862                            rank.Video = conditionsSatisfied ? 1 : 2;
 148863                        }
 148864                    }
 148865
 148866                    if (options.AllowAudioStreamCopy)
 148867                    {
 148868                        if (ContainerHelper.ContainsContainer(transcodingProfile.AudioCodec, audioCodec))
 148869                        {
 148870                            var appliedVideoConditions = options.Profile.CodecProfiles
 148871                                .Where(i => i.Type == CodecType.VideoAudio &&
 148872                                    i.ContainsAnyCodec(audioCodec, container) &&
 148873                                    i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoAudioConditionSati
 148874                                .Select(i =>
 148875                                    i.Conditions.All(condition => ConditionProcessor.IsVideoAudioConditionSatisfied(cond
 148876
 148877                            // An empty appliedVideoConditions means that the codec has no conditions for the current au
 148878                            var conditionsSatisfied = appliedVideoConditions.All(satisfied => satisfied);
 148879                            rank.Audio = conditionsSatisfied ? 1 : 2;
 148880                        }
 148881                    }
 148882
 148883                    PlayMethod playMethod = PlayMethod.Transcode;
 148884
 148885                    if (rank.Video == 1)
 148886                    {
 148887                        playMethod = PlayMethod.DirectStream;
 148888                    }
 148889
 148890                    return (Profile: transcodingProfile, PlayMethod: playMethod, Rank: rank);
 148891                })
 148892                .OrderBy(analysis => analysis.Rank);
 893
 148894            var profileMatch = analyzedProfiles.FirstOrDefault();
 895
 148896            return (profileMatch.Profile, profileMatch.PlayMethod);
 897        }
 898
 899        private void BuildStreamVideoItem(
 900            StreamInfo playlistItem,
 901            MediaOptions options,
 902            MediaSourceInfo item,
 903            MediaStream? videoStream,
 904            MediaStream? audioStream,
 905            IEnumerable<MediaStream> candidateAudioStreams,
 906            string? container,
 907            string? videoCodec,
 908            string? audioCodec)
 909        {
 910            // Prefer matching video codecs
 140911            var videoCodecs = ContainerHelper.Split(videoCodec).ToList();
 912
 140913            if (videoCodecs.Count == 0 && videoStream is not null)
 914            {
 915                // Add the original codec if no codec is specified
 0916                videoCodecs.Add(videoStream.Codec);
 917            }
 918
 919            // Enforce HLS video codec restrictions
 140920            if (playlistItem.SubProtocol == MediaStreamProtocol.hls)
 921            {
 126922                videoCodecs = videoCodecs.Where(codec => _supportedHlsVideoCodecs.Contains(codec)).ToList();
 923            }
 924
 140925            playlistItem.VideoCodecs = videoCodecs;
 926
 927            // Copy video codec options as a starting point, this applies to transcode and direct-stream
 140928            playlistItem.MaxFramerate = videoStream?.ReferenceFrameRate;
 140929            var qualifier = videoStream?.Codec;
 140930            if (videoStream?.Level is not null)
 931            {
 139932                playlistItem.SetOption(qualifier, "level", videoStream.Level.Value.ToString(CultureInfo.InvariantCulture
 933            }
 934
 140935            if (videoStream?.BitDepth is not null)
 936            {
 139937                playlistItem.SetOption(qualifier, "videobitdepth", videoStream.BitDepth.Value.ToString(CultureInfo.Invar
 938            }
 939
 140940            if (!string.IsNullOrEmpty(videoStream?.Profile))
 941            {
 139942                playlistItem.SetOption(qualifier, "profile", videoStream.Profile.ToLowerInvariant());
 943            }
 944
 945            // Prefer matching audio codecs, could do better here
 140946            var audioCodecs = ContainerHelper.Split(audioCodec).ToList();
 947
 140948            if (audioCodecs.Count == 0 && audioStream is not null)
 949            {
 950                // Add the original codec if no codec is specified
 0951                audioCodecs.Add(audioStream.Codec);
 952            }
 953
 954            // Enforce HLS audio codec restrictions
 140955            if (playlistItem.SubProtocol == MediaStreamProtocol.hls)
 956            {
 126957                if (string.Equals(playlistItem.Container, "mp4", StringComparison.OrdinalIgnoreCase))
 958                {
 72959                    audioCodecs = audioCodecs.Where(codec => _supportedHlsAudioCodecsMp4.Contains(codec)).ToList();
 960                }
 961                else
 962                {
 54963                    audioCodecs = audioCodecs.Where(codec => _supportedHlsAudioCodecsTs.Contains(codec)).ToList();
 964                }
 965            }
 966
 140967            var audioStreamWithSupportedCodec = candidateAudioStreams.Where(stream => ContainerHelper.ContainsContainer(
 968
 140969            var directAudioStream = audioStreamWithSupportedCodec?.Channels is not null && audioStreamWithSupportedCodec
 970
 140971            var channelsExceedsLimit = audioStreamWithSupportedCodec is not null && directAudioStream is null;
 972
 140973            if (channelsExceedsLimit && playlistItem.TargetAudioStream is not null)
 974            {
 8975                playlistItem.TranscodeReasons |= TranscodeReason.AudioChannelsNotSupported;
 8976                playlistItem.TargetAudioStream.Channels = playlistItem.TranscodingMaxAudioChannels;
 977            }
 978
 140979            playlistItem.AudioCodecs = audioCodecs;
 140980            if (directAudioStream is not null)
 981            {
 66982                audioStream = directAudioStream;
 66983                playlistItem.AudioStreamIndex = audioStream.Index;
 66984                audioCodecs = [audioStream.Codec];
 66985                playlistItem.AudioCodecs = audioCodecs;
 986
 987                // Copy matching audio codec options
 66988                playlistItem.AudioSampleRate = audioStream.SampleRate;
 66989                playlistItem.SetOption(qualifier, "audiochannels", audioStream.Channels?.ToString(CultureInfo.InvariantC
 990
 66991                if (!string.IsNullOrEmpty(audioStream.Profile))
 992                {
 56993                    playlistItem.SetOption(audioStream.Codec, "profile", audioStream.Profile.ToLowerInvariant());
 994                }
 995
 66996                if (audioStream.Level.HasValue && audioStream.Level.Value != 0)
 997                {
 0998                    playlistItem.SetOption(audioStream.Codec, "level", audioStream.Level.Value.ToString(CultureInfo.Inva
 999                }
 1000            }
 1001
 1401002            int? width = videoStream?.Width;
 1401003            int? height = videoStream?.Height;
 1401004            int? bitDepth = videoStream?.BitDepth;
 1401005            int? videoBitrate = videoStream?.BitRate;
 1401006            double? videoLevel = videoStream?.Level;
 1401007            string? videoProfile = videoStream?.Profile;
 1401008            VideoRangeType? videoRangeType = videoStream?.VideoRangeType;
 1401009            float videoFramerate = videoStream is null ? 0 : videoStream.ReferenceFrameRate ?? 0;
 1401010            bool? isAnamorphic = videoStream?.IsAnamorphic;
 1401011            bool? isInterlaced = videoStream?.IsInterlaced;
 1401012            string? videoCodecTag = videoStream?.CodecTag;
 1401013            bool? isAvc = videoStream?.IsAVC;
 1014
 1401015            TransportStreamTimestamp? timestamp = videoStream is null ? TransportStreamTimestamp.None : item.Timestamp;
 1401016            int? packetLength = videoStream?.PacketLength;
 1401017            int? refFrames = videoStream?.RefFrames;
 1018
 1401019            int? numAudioStreams = item.GetStreamCount(MediaStreamType.Audio);
 1401020            int? numVideoStreams = item.GetStreamCount(MediaStreamType.Video);
 1021
 1401022            var useSubContainer = playlistItem.SubProtocol == MediaStreamProtocol.hls;
 1023
 1401024            var appliedVideoConditions = options.Profile.CodecProfiles
 1401025                .Where(i => i.Type == CodecType.Video &&
 1401026                    i.ContainsAnyCodec(playlistItem.VideoCodecs, container, useSubContainer) &&
 1401027                    i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoConditionSatisfied(applyCondition,
 1401028                // Reverse codec profiles for backward compatibility - first codec profile has higher priority
 1401029                .Reverse();
 8461030            foreach (var condition in appliedVideoConditions)
 1031            {
 23261032                foreach (var transcodingVideoCodec in playlistItem.VideoCodecs)
 1033                {
 8801034                    if (condition.ContainsAnyCodec(transcodingVideoCodec, container, useSubContainer))
 1035                    {
 2881036                        ApplyTranscodingConditions(playlistItem, condition.Conditions, transcodingVideoCodec, true, true
 1037                        continue;
 1038                    }
 1039                }
 1040            }
 1041
 1042            // Honor requested max channels
 1401043            playlistItem.GlobalMaxAudioChannels = channelsExceedsLimit ? playlistItem.TranscodingMaxAudioChannels : opti
 1044
 1401045            int audioBitrate = GetAudioBitrate(options.GetMaxBitrate(true) ?? 0, playlistItem.TargetAudioCodec, audioStr
 1401046            playlistItem.AudioBitrate = Math.Min(playlistItem.AudioBitrate ?? audioBitrate, audioBitrate);
 1047
 1401048            bool? isSecondaryAudio = audioStream is null ? null : item.IsSecondaryAudio(audioStream);
 1401049            int? inputAudioBitrate = audioStream?.BitRate;
 1401050            int? audioChannels = audioStream?.Channels;
 1401051            string? audioProfile = audioStream?.Profile;
 1401052            int? inputAudioSampleRate = audioStream?.SampleRate;
 1401053            int? inputAudioBitDepth = audioStream?.BitDepth;
 1054
 1401055            var appliedAudioConditions = options.Profile.CodecProfiles
 1401056                .Where(i => i.Type == CodecType.VideoAudio &&
 1401057                    i.ContainsAnyCodec(playlistItem.AudioCodecs, container) &&
 1401058                    i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoAudioConditionSatisfied(applyCondi
 1401059                // Reverse codec profiles for backward compatibility - first codec profile has higher priority
 1401060                .Reverse();
 1061
 5721062            foreach (var codecProfile in appliedAudioConditions)
 1063            {
 4381064                foreach (var transcodingAudioCodec in playlistItem.AudioCodecs)
 1065                {
 1461066                    if (codecProfile.ContainsAnyCodec(transcodingAudioCodec, container))
 1067                    {
 1461068                        ApplyTranscodingConditions(playlistItem, codecProfile.Conditions, transcodingAudioCodec, true, t
 1461069                        break;
 1070                    }
 1071                }
 1072            }
 1073
 1401074            var maxBitrateSetting = options.GetMaxBitrate(false);
 1075            // Honor max rate
 1401076            if (maxBitrateSetting.HasValue)
 1077            {
 1401078                var availableBitrateForVideo = maxBitrateSetting.Value;
 1079
 1401080                if (playlistItem.AudioBitrate.HasValue)
 1081                {
 1401082                    availableBitrateForVideo -= playlistItem.AudioBitrate.Value;
 1083                }
 1084
 1085                // Make sure the video bitrate is lower than bitrate settings but at least 64k
 1086                // Don't use Math.Clamp as availableBitrateForVideo can be lower then 64k.
 1401087                var currentValue = playlistItem.VideoBitrate ?? availableBitrateForVideo;
 1401088                playlistItem.VideoBitrate = Math.Max(Math.Min(availableBitrateForVideo, currentValue), 64_000);
 1089            }
 1090
 1401091            _logger.LogDebug(
 1401092                "Transcode Result for Profile: {Profile}, Path: {Path}, PlayMethod: {PlayMethod}, AudioStreamIndex: {Aud
 1401093                options.Profile?.Name ?? "Anonymous Profile",
 1401094                item.Path ?? "Unknown path",
 1401095                playlistItem?.PlayMethod,
 1401096                audioStream?.Index,
 1401097                playlistItem?.SubtitleStreamIndex,
 1401098                playlistItem?.TranscodeReasons);
 1401099        }
 1100
 1101        private static int GetDefaultAudioBitrate(string? audioCodec, int? audioChannels)
 1102        {
 601103            if (!string.IsNullOrEmpty(audioCodec))
 1104            {
 1105                // Default to a higher bitrate for stream copy
 601106                if (string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase)
 601107                    || string.Equals(audioCodec, "mp3", StringComparison.OrdinalIgnoreCase)
 601108                    || string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase)
 601109                    || string.Equals(audioCodec, "eac3", StringComparison.OrdinalIgnoreCase))
 1110                {
 551111                    if ((audioChannels ?? 0) < 2)
 1112                    {
 01113                        return 128000;
 1114                    }
 1115
 551116                    return (audioChannels ?? 0) >= 6 ? 640000 : 384000;
 1117                }
 1118
 51119                if (string.Equals(audioCodec, "flac", StringComparison.OrdinalIgnoreCase)
 51120                    || string.Equals(audioCodec, "alac", StringComparison.OrdinalIgnoreCase))
 1121                {
 01122                    if ((audioChannels ?? 0) < 2)
 1123                    {
 01124                        return 768000;
 1125                    }
 1126
 01127                    return (audioChannels ?? 0) >= 6 ? 3584000 : 1536000;
 1128                }
 1129            }
 1130
 51131            return 192000;
 1132        }
 1133
 1134        private static int GetAudioBitrate(long maxTotalBitrate, IReadOnlyList<string> targetAudioCodecs, MediaStream? a
 1135        {
 1401136            string? targetAudioCodec = targetAudioCodecs.Count == 0 ? null : targetAudioCodecs[0];
 1137
 1401138            int? targetAudioChannels = item.GetTargetAudioChannels(targetAudioCodec);
 1139
 1140            int defaultBitrate;
 1401141            int encoderAudioBitrateLimit = int.MaxValue;
 1142
 1401143            if (audioStream is null)
 1144            {
 11145                defaultBitrate = 192000;
 1146            }
 1147            else
 1148            {
 1391149                if (targetAudioChannels.HasValue
 1391150                    && audioStream.Channels.HasValue
 1391151                    && audioStream.Channels.Value > targetAudioChannels.Value)
 1152                {
 1153                    // Reduce the bitrate if we're down mixing.
 461154                    defaultBitrate = GetDefaultAudioBitrate(targetAudioCodec, targetAudioChannels);
 1155                }
 931156                else if (targetAudioChannels.HasValue
 931157                         && audioStream.Channels.HasValue
 931158                         && audioStream.Channels.Value <= targetAudioChannels.Value
 931159                         && !string.IsNullOrEmpty(audioStream.Codec)
 931160                         && targetAudioCodecs is not null
 931161                         && targetAudioCodecs.Count > 0
 931162                         && !targetAudioCodecs.Any(elem => string.Equals(audioStream.Codec, elem, StringComparison.Ordin
 1163                {
 1164                    // Shift the bitrate if we're transcoding to a different audio codec.
 141165                    defaultBitrate = GetDefaultAudioBitrate(targetAudioCodec, audioStream.Channels.Value);
 1166                }
 1167                else
 1168                {
 791169                    defaultBitrate = audioStream.BitRate ?? GetDefaultAudioBitrate(targetAudioCodec, targetAudioChannels
 1170                }
 1171
 1172                // Seeing webm encoding failures when source has 1 audio channel and 22k bitrate.
 1173                // Any attempts to transcode over 64k will fail
 1391174                if (audioStream.Channels == 1
 1391175                    && (audioStream.BitRate ?? 0) < 64000)
 1176                {
 01177                    encoderAudioBitrateLimit = 64000;
 1178                }
 1179            }
 1180
 1401181            if (maxTotalBitrate > 0)
 1182            {
 1401183                defaultBitrate = Math.Min(GetMaxAudioBitrateForTotalBitrate(maxTotalBitrate), defaultBitrate);
 1184            }
 1185
 1401186            return Math.Min(defaultBitrate, encoderAudioBitrateLimit);
 1187        }
 1188
 1189        private static int GetMaxAudioBitrateForTotalBitrate(long totalBitrate)
 1190        {
 1401191            if (totalBitrate <= 640000)
 1192            {
 81193                return 128000;
 1194            }
 1195
 1321196            if (totalBitrate <= 2000000)
 1197            {
 01198                return 384000;
 1199            }
 1200
 1321201            if (totalBitrate <= 3000000)
 1202            {
 01203                return 448000;
 1204            }
 1205
 1321206            if (totalBitrate <= 4000000)
 1207            {
 01208                return 640000;
 1209            }
 1210
 1321211            if (totalBitrate <= 5000000)
 1212            {
 01213                return 768000;
 1214            }
 1215
 1321216            if (totalBitrate <= 10000000)
 1217            {
 81218                return 1536000;
 1219            }
 1220
 1241221            if (totalBitrate <= 15000000)
 1222            {
 01223                return 2304000;
 1224            }
 1225
 1241226            if (totalBitrate <= 20000000)
 1227            {
 141228                return 3584000;
 1229            }
 1230
 1101231            return 7168000;
 1232        }
 1233
 1234        private (DirectPlayProfile? Profile, PlayMethod? PlayMethod, int? AudioStreamIndex, TranscodeReason TranscodeRea
 1235            MediaOptions options,
 1236            MediaSourceInfo mediaSource,
 1237            MediaStream? videoStream,
 1238            MediaStream? audioStream,
 1239            ICollection<MediaStream> candidateAudioStreams,
 1240            MediaStream? subtitleStream,
 1241            bool isEligibleForDirectPlay,
 1242            bool isEligibleForDirectStream)
 1243        {
 2401244            if (options.ForceDirectPlay)
 1245            {
 01246                return (null, PlayMethod.DirectPlay, audioStream?.Index, 0);
 1247            }
 1248
 2401249            if (options.ForceDirectStream)
 1250            {
 01251                return (null, PlayMethod.DirectStream, audioStream?.Index, 0);
 1252            }
 1253
 2401254            DeviceProfile profile = options.Profile;
 2401255            string container = mediaSource.Container;
 1256
 1257            // Video
 2401258            int? width = videoStream?.Width;
 2401259            int? height = videoStream?.Height;
 2401260            int? bitDepth = videoStream?.BitDepth;
 2401261            int? videoBitrate = videoStream?.BitRate;
 2401262            double? videoLevel = videoStream?.Level;
 2401263            string? videoProfile = videoStream?.Profile;
 2401264            VideoRangeType? videoRangeType = videoStream?.VideoRangeType;
 2401265            float videoFramerate = videoStream is null ? 0 : videoStream.ReferenceFrameRate ?? 0;
 2401266            bool? isAnamorphic = videoStream?.IsAnamorphic;
 2401267            bool? isInterlaced = videoStream?.IsInterlaced;
 2401268            string? videoCodecTag = videoStream?.CodecTag;
 2401269            bool? isAvc = videoStream?.IsAVC;
 1270
 2401271            TransportStreamTimestamp? timestamp = videoStream is null ? TransportStreamTimestamp.None : mediaSource.Time
 2401272            int? packetLength = videoStream?.PacketLength;
 2401273            int? refFrames = videoStream?.RefFrames;
 1274
 2401275            int? numAudioStreams = mediaSource.GetStreamCount(MediaStreamType.Audio);
 2401276            int? numVideoStreams = mediaSource.GetStreamCount(MediaStreamType.Video);
 1277
 2401278            var checkVideoConditions = (ProfileCondition[] conditions) =>
 2401279                conditions.Where(applyCondition => !ConditionProcessor.IsVideoConditionSatisfied(applyCondition, width, 
 1280
 1281            // Check container conditions
 2401282            var containerProfileReasons = AggregateFailureConditions(
 2401283                mediaSource,
 2401284                profile,
 2401285                "VideoCodecProfile",
 2401286                profile.ContainerProfiles
 2401287                    .Where(containerProfile => containerProfile.Type == DlnaProfileType.Video && containerProfile.Contai
 2401288                    .SelectMany(containerProfile => checkVideoConditions(containerProfile.Conditions)));
 1289
 1290            // Check video conditions
 2401291            var videoCodecProfileReasons = AggregateFailureConditions(
 2401292                mediaSource,
 2401293                profile,
 2401294                "VideoCodecProfile",
 2401295                profile.CodecProfiles
 2401296                    .Where(codecProfile => codecProfile.Type == CodecType.Video &&
 2401297                        codecProfile.ContainsAnyCodec(videoStream?.Codec, container) &&
 2401298                        !checkVideoConditions(codecProfile.ApplyConditions).Any())
 2401299                    .SelectMany(codecProfile => checkVideoConditions(codecProfile.Conditions)));
 1300
 1301            // Check audio candidates profile conditions
 2401302            var audioStreamMatches = candidateAudioStreams.ToDictionary(s => s, audioStream => CheckVideoAudioStreamDire
 1303
 2401304            TranscodeReason subtitleProfileReasons = 0;
 2401305            if (subtitleStream is not null)
 1306            {
 2171307                var subtitleProfile = GetSubtitleProfile(mediaSource, subtitleStream, options.Profile.SubtitleProfiles, 
 1308
 2171309                if (subtitleProfile.Method != SubtitleDeliveryMethod.Drop
 2171310                    && subtitleProfile.Method != SubtitleDeliveryMethod.External
 2171311                    && subtitleProfile.Method != SubtitleDeliveryMethod.Embed)
 1312                {
 01313                    _logger.LogDebug("Not eligible for {0} due to unsupported subtitles", PlayMethod.DirectPlay);
 01314                    subtitleProfileReasons |= TranscodeReason.SubtitleCodecNotSupported;
 1315                }
 1316            }
 1317
 2401318            var containerSupported = false;
 2401319            TranscodeReason[] rankings = [TranscodeReason.VideoCodecNotSupported, VideoCodecReasons, TranscodeReason.Aud
 1320
 1321            // Check DirectPlay profiles to see if it can be direct played
 2401322            var analyzedProfiles = profile.DirectPlayProfiles
 2401323                .Where(directPlayProfile => directPlayProfile.Type == DlnaProfileType.Video)
 2401324                .Select((directPlayProfile, order) =>
 2401325                {
 2401326                    TranscodeReason directPlayProfileReasons = 0;
 2401327                    TranscodeReason audioCodecProfileReasons = 0;
 2401328
 2401329                    // Check container type
 2401330                    if (!directPlayProfile.SupportsContainer(container))
 2401331                    {
 2401332                        directPlayProfileReasons |= TranscodeReason.ContainerNotSupported;
 2401333                    }
 2401334                    else
 2401335                    {
 2401336                        containerSupported = true;
 2401337                    }
 2401338
 2401339                    // Check video codec
 2401340                    string? videoCodec = videoStream?.Codec;
 2401341                    if (!directPlayProfile.SupportsVideoCodec(videoCodec))
 2401342                    {
 2401343                        directPlayProfileReasons |= TranscodeReason.VideoCodecNotSupported;
 2401344                    }
 2401345
 2401346                    // Check audio codec
 2401347                    MediaStream? selectedAudioStream = null;
 2401348                    if (candidateAudioStreams.Count != 0)
 2401349                    {
 2401350                        selectedAudioStream = candidateAudioStreams.FirstOrDefault(audioStream => directPlayProfile.Supp
 2401351                        if (selectedAudioStream is null)
 2401352                        {
 2401353                            directPlayProfileReasons |= TranscodeReason.AudioCodecNotSupported;
 2401354                        }
 2401355                        else
 2401356                        {
 2401357                            audioCodecProfileReasons = audioStreamMatches.GetValueOrDefault(selectedAudioStream);
 2401358                        }
 2401359                    }
 2401360
 2401361                    var failureReasons = directPlayProfileReasons | containerProfileReasons | subtitleProfileReasons;
 2401362
 2401363                    if ((failureReasons & TranscodeReason.VideoCodecNotSupported) == 0)
 2401364                    {
 2401365                        failureReasons |= videoCodecProfileReasons;
 2401366                    }
 2401367
 2401368                    if ((failureReasons & TranscodeReason.AudioCodecNotSupported) == 0)
 2401369                    {
 2401370                        failureReasons |= audioCodecProfileReasons;
 2401371                    }
 2401372
 2401373                    var directStreamFailureReasons = failureReasons & (~DirectStreamReasons);
 2401374
 2401375                    PlayMethod? playMethod = null;
 2401376                    if (failureReasons == 0 && isEligibleForDirectPlay && mediaSource.SupportsDirectPlay)
 2401377                    {
 2401378                        playMethod = PlayMethod.DirectPlay;
 2401379                    }
 2401380                    else if (directStreamFailureReasons == 0 && isEligibleForDirectStream && mediaSource.SupportsDirectS
 2401381                    {
 2401382                        playMethod = PlayMethod.DirectStream;
 2401383                    }
 2401384
 2401385                    var ranked = GetRank(ref failureReasons, rankings);
 2401386
 2401387                    return (Result: (Profile: directPlayProfile, PlayMethod: playMethod, AudioStreamIndex: selectedAudio
 2401388                })
 2401389                .OrderByDescending(analysis => analysis.Result.PlayMethod)
 2401390                .ThenByDescending(analysis => analysis.Rank)
 2401391                .ThenBy(analysis => analysis.Order)
 2401392                .ToArray()
 2401393                .ToLookup(analysis => analysis.Result.PlayMethod is not null);
 1394
 2401395            var profileMatch = analyzedProfiles[true]
 2401396                .Select(analysis => analysis.Result)
 2401397                .FirstOrDefault();
 2401398            if (profileMatch.Profile is not null)
 1399            {
 1161400                return profileMatch;
 1401            }
 1402
 1241403            var failureReasons = analyzedProfiles[false]
 1241404                .Select(analysis => analysis.Result)
 1241405                .Where(result => !containerSupported || !result.TranscodeReason.HasFlag(TranscodeReason.ContainerNotSupp
 1241406                .FirstOrDefault().TranscodeReason;
 1241407            if (failureReasons == 0)
 1408            {
 141409                failureReasons = TranscodeReason.DirectPlayError;
 1410            }
 1411
 1241412            return (Profile: null, PlayMethod: null, AudioStreamIndex: null, TranscodeReasons: failureReasons);
 1413        }
 1414
 1415        private TranscodeReason CheckVideoAudioStreamDirectPlay(MediaOptions options, MediaSourceInfo mediaSource, strin
 1416        {
 2591417            var profile = options.Profile;
 2591418            var audioFailureConditions = GetProfileConditionsForVideoAudio(profile.CodecProfiles, container, audioStream
 1419
 2591420            var audioStreamFailureReasons = AggregateFailureConditions(mediaSource, profile, "VideoAudioCodecProfile", a
 2591421            if (audioStream.IsExternal == true)
 1422            {
 61423                audioStreamFailureReasons |= TranscodeReason.AudioIsExternal;
 1424            }
 1425
 2591426            return audioStreamFailureReasons;
 1427        }
 1428
 1429        private TranscodeReason AggregateFailureConditions(MediaSourceInfo mediaSource, DeviceProfile profile, string ty
 1430        {
 7391431            return conditions.Aggregate<ProfileCondition, TranscodeReason>(0, (reasons, i) =>
 7391432            {
 7391433                LogConditionFailure(profile, type, i, mediaSource);
 7391434                var transcodeReasons = GetTranscodeReasonForFailedCondition(i);
 7391435                return reasons | transcodeReasons;
 7391436            });
 1437        }
 1438
 1439        private void LogConditionFailure(DeviceProfile profile, string type, ProfileCondition condition, MediaSourceInfo
 1440        {
 1071441            _logger.LogDebug(
 1071442                "Profile: {0}, DirectPlay=false. Reason={1}.{2} Condition: {3}. ConditionValue: {4}. IsRequired: {5}. Pa
 1071443                type,
 1071444                profile.Name ?? "Unknown Profile",
 1071445                condition.Property,
 1071446                condition.Condition,
 1071447                condition.Value ?? string.Empty,
 1071448                condition.IsRequired,
 1071449                mediaSource.Path ?? "Unknown path");
 1071450        }
 1451
 1452        /// <summary>
 1453        /// Normalizes input container.
 1454        /// </summary>
 1455        /// <param name="mediaSource">The <see cref="MediaSourceInfo"/>.</param>
 1456        /// <param name="subtitleStream">The <see cref="MediaStream"/> of the subtitle stream.</param>
 1457        /// <param name="subtitleProfiles">The list of supported <see cref="SubtitleProfile"/>s.</param>
 1458        /// <param name="playMethod">The <see cref="PlayMethod"/>.</param>
 1459        /// <param name="transcoderSupport">The <see cref="ITranscoderSupport"/>.</param>
 1460        /// <param name="outputContainer">The output container.</param>
 1461        /// <param name="transcodingSubProtocol">The subtitle transcoding protocol.</param>
 1462        /// <returns>The normalized input container.</returns>
 1463        public static SubtitleProfile GetSubtitleProfile(
 1464            MediaSourceInfo mediaSource,
 1465            MediaStream subtitleStream,
 1466            SubtitleProfile[] subtitleProfiles,
 1467            PlayMethod playMethod,
 1468            ITranscoderSupport transcoderSupport,
 1469            string? outputContainer,
 1470            MediaStreamProtocol? transcodingSubProtocol)
 1471        {
 4501472            if (!subtitleStream.IsExternal && (playMethod != PlayMethod.Transcode || transcodingSubProtocol != MediaStre
 1473            {
 1474                // Look for supported embedded subs of the same format
 01475                foreach (var profile in subtitleProfiles)
 1476                {
 01477                    if (!profile.SupportsLanguage(subtitleStream.Language))
 1478                    {
 1479                        continue;
 1480                    }
 1481
 01482                    if (profile.Method != SubtitleDeliveryMethod.Embed)
 1483                    {
 1484                        continue;
 1485                    }
 1486
 01487                    if (!ContainerHelper.ContainsContainer(profile.Container, outputContainer))
 1488                    {
 1489                        continue;
 1490                    }
 1491
 01492                    if (playMethod == PlayMethod.Transcode && !IsSubtitleEmbedSupported(outputContainer))
 1493                    {
 1494                        continue;
 1495                    }
 1496
 01497                    if (subtitleStream.IsTextSubtitleStream == MediaStream.IsTextFormat(profile.Format) && string.Equals
 1498                    {
 01499                        return profile;
 1500                    }
 1501                }
 1502
 1503                // Look for supported embedded subs of a convertible format
 01504                foreach (var profile in subtitleProfiles)
 1505                {
 01506                    if (!profile.SupportsLanguage(subtitleStream.Language))
 1507                    {
 1508                        continue;
 1509                    }
 1510
 01511                    if (profile.Method != SubtitleDeliveryMethod.Embed)
 1512                    {
 1513                        continue;
 1514                    }
 1515
 01516                    if (!ContainerHelper.ContainsContainer(profile.Container, outputContainer))
 1517                    {
 1518                        continue;
 1519                    }
 1520
 01521                    if (playMethod == PlayMethod.Transcode && !IsSubtitleEmbedSupported(outputContainer))
 1522                    {
 1523                        continue;
 1524                    }
 1525
 01526                    if (subtitleStream.IsTextSubtitleStream && subtitleStream.SupportsSubtitleConversionTo(profile.Forma
 1527                    {
 01528                        return profile;
 1529                    }
 1530                }
 1531            }
 1532
 1533            // Look for an external or hls profile that matches the stream type (text/graphical) and doesn't require con
 4501534            return GetExternalSubtitleProfile(mediaSource, subtitleStream, subtitleProfiles, playMethod, transcoderSuppo
 4501535                GetExternalSubtitleProfile(mediaSource, subtitleStream, subtitleProfiles, playMethod, transcoderSupport,
 4501536                new SubtitleProfile
 4501537                {
 4501538                    Method = SubtitleDeliveryMethod.Encode,
 4501539                    Format = subtitleStream.Codec
 4501540                };
 1541        }
 1542
 1543        private static bool IsSubtitleEmbedSupported(string? transcodingContainer)
 1544        {
 01545            if (!string.IsNullOrEmpty(transcodingContainer))
 1546            {
 01547                if (ContainerHelper.ContainsContainer(transcodingContainer, "ts,mpegts,mp4"))
 1548                {
 01549                    return false;
 1550                }
 1551
 01552                if (ContainerHelper.ContainsContainer(transcodingContainer, "mkv,matroska"))
 1553                {
 01554                    return true;
 1555                }
 1556            }
 1557
 01558            return false;
 1559        }
 1560
 1561        private static SubtitleProfile? GetExternalSubtitleProfile(MediaSourceInfo mediaSource, MediaStream subtitleStre
 1562        {
 49341563            foreach (var profile in subtitleProfiles)
 1564            {
 19681565                if (profile.Method != SubtitleDeliveryMethod.External && profile.Method != SubtitleDeliveryMethod.Hls)
 1566                {
 1567                    continue;
 1568                }
 1569
 13341570                if (profile.Method == SubtitleDeliveryMethod.Hls && playMethod != PlayMethod.Transcode)
 1571                {
 1572                    continue;
 1573                }
 1574
 13341575                if (!profile.SupportsLanguage(subtitleStream.Language))
 1576                {
 1577                    continue;
 1578                }
 1579
 13341580                if (!subtitleStream.IsExternal && !transcoderSupport.CanExtractSubtitles(subtitleStream.Codec))
 1581                {
 1582                    continue;
 1583                }
 1584
 13341585                if ((profile.Method == SubtitleDeliveryMethod.External && subtitleStream.IsTextSubtitleStream == MediaSt
 13341586                    (profile.Method == SubtitleDeliveryMethod.Hls && subtitleStream.IsTextSubtitleStream))
 1587                {
 13161588                    bool requiresConversion = !string.Equals(subtitleStream.Codec, profile.Format, StringComparison.Ordi
 1589
 13161590                    if (!requiresConversion)
 1591                    {
 1761592                        return profile;
 1593                    }
 1594
 11401595                    if (!allowConversion)
 1596                    {
 1597                        continue;
 1598                    }
 1599
 1600                    // TODO: Build this into subtitleStream.SupportsExternalStream
 2741601                    if (mediaSource.IsInfiniteStream)
 1602                    {
 1603                        continue;
 1604                    }
 1605
 2741606                    if (subtitleStream.IsTextSubtitleStream && subtitleStream.SupportsExternalStream && subtitleStream.S
 1607                    {
 2741608                        return profile;
 1609                    }
 1610                }
 1611            }
 1612
 2741613            return null;
 1614        }
 1615
 1616        private bool IsBitrateLimitExceeded(MediaSourceInfo item, long maxBitrate)
 1617        {
 1618            // Don't restrict bitrate if item is remote.
 2641619            if (item.IsRemote)
 1620            {
 01621                return false;
 1622            }
 1623
 1624            // If no maximum bitrate is set, default to no maximum bitrate.
 2641625            long requestedMaxBitrate = maxBitrate > 0 ? maxBitrate : int.MaxValue;
 1626
 1627            // If we don't know the item bitrate, then force a transcode if requested max bitrate is under 40 mbps
 2641628            int itemBitrate = item.Bitrate ?? 40000000;
 1629
 2641630            if (itemBitrate > requestedMaxBitrate)
 1631            {
 241632                _logger.LogDebug(
 241633                    "Bitrate exceeds limit: media bitrate: {MediaBitrate}, max bitrate: {MaxBitrate}",
 241634                    itemBitrate,
 241635                    requestedMaxBitrate);
 241636                return true;
 1637            }
 1638
 2401639            return false;
 1640        }
 1641
 1642        private static void ValidateMediaOptions(MediaOptions options, bool isMediaSource)
 1643        {
 2641644            if (options.ItemId.IsEmpty())
 1645            {
 01646                ArgumentException.ThrowIfNullOrEmpty(options.DeviceId);
 1647            }
 1648
 2641649            if (options.Profile is null)
 1650            {
 01651                throw new ArgumentException("Profile is required");
 1652            }
 1653
 2641654            if (options.MediaSources is null)
 1655            {
 01656                throw new ArgumentException("MediaSources is required");
 1657            }
 1658
 2641659            if (isMediaSource)
 1660            {
 2641661                if (options.AudioStreamIndex.HasValue && string.IsNullOrEmpty(options.MediaSourceId))
 1662                {
 01663                    throw new ArgumentException("MediaSourceId is required when a specific audio stream is requested");
 1664                }
 1665
 2641666                if (options.SubtitleStreamIndex.HasValue && string.IsNullOrEmpty(options.MediaSourceId))
 1667                {
 01668                    throw new ArgumentException("MediaSourceId is required when a specific subtitle stream is requested"
 1669                }
 1670            }
 2641671        }
 1672
 1673        private static IEnumerable<ProfileCondition> GetProfileConditionsForVideoAudio(
 1674            IEnumerable<CodecProfile> codecProfiles,
 1675            string container,
 1676            string codec,
 1677            int? audioChannels,
 1678            int? audioBitrate,
 1679            int? audioSampleRate,
 1680            int? audioBitDepth,
 1681            string audioProfile,
 1682            bool? isSecondaryAudio)
 1683        {
 2591684            return codecProfiles
 2591685                .Where(profile => profile.Type == CodecType.VideoAudio &&
 2591686                    profile.ContainsAnyCodec(codec, container) &&
 2591687                    profile.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoAudioConditionSatisfied(appl
 2591688                .SelectMany(profile => profile.Conditions)
 2591689                .Where(condition => !ConditionProcessor.IsVideoAudioConditionSatisfied(condition, audioChannels, audioBi
 1690        }
 1691
 1692        private static IEnumerable<ProfileCondition> GetProfileConditionsForAudio(
 1693            IEnumerable<CodecProfile> codecProfiles,
 1694            string container,
 1695            string? codec,
 1696            int? audioChannels,
 1697            int? audioBitrate,
 1698            int? audioSampleRate,
 1699            int? audioBitDepth,
 1700            bool checkConditions)
 1701        {
 01702            var conditions = codecProfiles
 01703                .Where(profile => profile.Type == CodecType.Audio &&
 01704                    profile.ContainsAnyCodec(codec, container) &&
 01705                    profile.ApplyConditions.All(applyCondition => ConditionProcessor.IsAudioConditionSatisfied(applyCond
 01706                .SelectMany(profile => profile.Conditions);
 1707
 01708            if (!checkConditions)
 1709            {
 01710                return conditions;
 1711            }
 1712
 01713            return conditions.Where(condition => !ConditionProcessor.IsAudioConditionSatisfied(condition, audioChannels,
 1714        }
 1715
 1716        private void ApplyTranscodingConditions(StreamInfo item, IEnumerable<ProfileCondition> conditions, string? quali
 1717        {
 32481718            foreach (ProfileCondition condition in conditions)
 1719            {
 11501720                string value = condition.Value;
 1721
 11501722                if (string.IsNullOrEmpty(value))
 1723                {
 1724                    continue;
 1725                }
 1726
 1727                // No way to express this
 11501728                if (condition.Condition == ProfileConditionType.GreaterThanEqual)
 1729                {
 1730                    continue;
 1731                }
 1732
 11501733                switch (condition.Property)
 1734                {
 1735                    case ProfileConditionValue.AudioBitrate:
 1736                        {
 01737                            if (!enableNonQualifiedConditions)
 1738                            {
 1739                                continue;
 1740                            }
 1741
 01742                            if (int.TryParse(value, CultureInfo.InvariantCulture, out var num))
 1743                            {
 01744                                if (condition.Condition == ProfileConditionType.Equals)
 1745                                {
 01746                                    item.AudioBitrate = num;
 1747                                }
 01748                                else if (condition.Condition == ProfileConditionType.LessThanEqual)
 1749                                {
 01750                                    item.AudioBitrate = Math.Min(num, item.AudioBitrate ?? num);
 1751                                }
 01752                                else if (condition.Condition == ProfileConditionType.GreaterThanEqual)
 1753                                {
 01754                                    item.AudioBitrate = Math.Max(num, item.AudioBitrate ?? num);
 1755                                }
 1756                            }
 1757
 01758                            break;
 1759                        }
 1760
 1761                    case ProfileConditionValue.AudioSampleRate:
 1762                        {
 01763                            if (!enableNonQualifiedConditions)
 1764                            {
 1765                                continue;
 1766                            }
 1767
 01768                            if (int.TryParse(value, CultureInfo.InvariantCulture, out var num))
 1769                            {
 01770                                if (condition.Condition == ProfileConditionType.Equals)
 1771                                {
 01772                                    item.AudioSampleRate = num;
 1773                                }
 01774                                else if (condition.Condition == ProfileConditionType.LessThanEqual)
 1775                                {
 01776                                    item.AudioSampleRate = Math.Min(num, item.AudioSampleRate ?? num);
 1777                                }
 01778                                else if (condition.Condition == ProfileConditionType.GreaterThanEqual)
 1779                                {
 01780                                    item.AudioSampleRate = Math.Max(num, item.AudioSampleRate ?? num);
 1781                                }
 1782                            }
 1783
 01784                            break;
 1785                        }
 1786
 1787                    case ProfileConditionValue.AudioChannels:
 1788                        {
 281789                            if (string.IsNullOrEmpty(qualifier))
 1790                            {
 01791                                if (!enableNonQualifiedConditions)
 1792                                {
 01793                                    continue;
 1794                                }
 1795                            }
 1796                            else
 1797                            {
 281798                                if (!enableQualifiedConditions)
 1799                                {
 1800                                    continue;
 1801                                }
 1802                            }
 1803
 281804                            if (int.TryParse(value, CultureInfo.InvariantCulture, out var num))
 1805                            {
 281806                                if (condition.Condition == ProfileConditionType.Equals)
 1807                                {
 01808                                    item.SetOption(qualifier, "audiochannels", num.ToString(CultureInfo.InvariantCulture
 1809                                }
 281810                                else if (condition.Condition == ProfileConditionType.LessThanEqual)
 1811                                {
 281812                                    item.SetOption(qualifier, "audiochannels", Math.Min(num, item.GetTargetAudioChannels
 1813                                }
 01814                                else if (condition.Condition == ProfileConditionType.GreaterThanEqual)
 1815                                {
 01816                                    item.SetOption(qualifier, "audiochannels", Math.Max(num, item.GetTargetAudioChannels
 1817                                }
 1818                            }
 1819
 01820                            break;
 1821                        }
 1822
 1823                    case ProfileConditionValue.IsAvc:
 1824                        {
 01825                            if (!enableNonQualifiedConditions)
 1826                            {
 1827                                continue;
 1828                            }
 1829
 01830                            if (bool.TryParse(value, out var isAvc))
 1831                            {
 01832                                if (isAvc && condition.Condition == ProfileConditionType.Equals)
 1833                                {
 01834                                    item.RequireAvc = true;
 1835                                }
 01836                                else if (!isAvc && condition.Condition == ProfileConditionType.NotEquals)
 1837                                {
 01838                                    item.RequireAvc = true;
 1839                                }
 1840                            }
 1841
 01842                            break;
 1843                        }
 1844
 1845                    case ProfileConditionValue.IsAnamorphic:
 1846                        {
 1891847                            if (!enableNonQualifiedConditions)
 1848                            {
 1849                                continue;
 1850                            }
 1851
 1891852                            if (bool.TryParse(value, out var isAnamorphic))
 1853                            {
 1891854                                if (isAnamorphic && condition.Condition == ProfileConditionType.Equals)
 1855                                {
 01856                                    item.RequireNonAnamorphic = true;
 1857                                }
 1891858                                else if (!isAnamorphic && condition.Condition == ProfileConditionType.NotEquals)
 1859                                {
 01860                                    item.RequireNonAnamorphic = true;
 1861                                }
 1862                            }
 1863
 01864                            break;
 1865                        }
 1866
 1867                    case ProfileConditionValue.IsInterlaced:
 1868                        {
 1091869                            if (string.IsNullOrEmpty(qualifier))
 1870                            {
 01871                                if (!enableNonQualifiedConditions)
 1872                                {
 01873                                    continue;
 1874                                }
 1875                            }
 1876                            else
 1877                            {
 1091878                                if (!enableQualifiedConditions)
 1879                                {
 1880                                    continue;
 1881                                }
 1882                            }
 1883
 1091884                            if (bool.TryParse(value, out var isInterlaced))
 1885                            {
 1091886                                if (!isInterlaced && condition.Condition == ProfileConditionType.Equals)
 1887                                {
 01888                                    item.SetOption(qualifier, "deinterlace", "true");
 1889                                }
 1091890                                else if (isInterlaced && condition.Condition == ProfileConditionType.NotEquals)
 1891                                {
 1091892                                    item.SetOption(qualifier, "deinterlace", "true");
 1893                                }
 1894                            }
 1895
 1091896                            break;
 1897                        }
 1898
 1899                    case ProfileConditionValue.AudioProfile:
 1900                    case ProfileConditionValue.Has64BitOffsets:
 1901                    case ProfileConditionValue.PacketLength:
 1902                    case ProfileConditionValue.NumAudioStreams:
 1903                    case ProfileConditionValue.NumVideoStreams:
 1904                    case ProfileConditionValue.IsSecondaryAudio:
 1905                    case ProfileConditionValue.VideoTimestamp:
 1906                        {
 1907                            // Not supported yet
 1908                            break;
 1909                        }
 1910
 1911                    case ProfileConditionValue.RefFrames:
 1912                        {
 21913                            if (string.IsNullOrEmpty(qualifier))
 1914                            {
 01915                                if (!enableNonQualifiedConditions)
 1916                                {
 01917                                    continue;
 1918                                }
 1919                            }
 1920                            else
 1921                            {
 21922                                if (!enableQualifiedConditions)
 1923                                {
 1924                                    continue;
 1925                                }
 1926                            }
 1927
 21928                            if (int.TryParse(value, CultureInfo.InvariantCulture, out var num))
 1929                            {
 21930                                if (condition.Condition == ProfileConditionType.Equals)
 1931                                {
 01932                                    item.SetOption(qualifier, "maxrefframes", num.ToString(CultureInfo.InvariantCulture)
 1933                                }
 21934                                else if (condition.Condition == ProfileConditionType.LessThanEqual)
 1935                                {
 21936                                    item.SetOption(qualifier, "maxrefframes", Math.Min(num, item.GetTargetRefFrames(qual
 1937                                }
 01938                                else if (condition.Condition == ProfileConditionType.GreaterThanEqual)
 1939                                {
 01940                                    item.SetOption(qualifier, "maxrefframes", Math.Max(num, item.GetTargetRefFrames(qual
 1941                                }
 1942                            }
 1943
 01944                            break;
 1945                        }
 1946
 1947                    case ProfileConditionValue.VideoBitDepth:
 1948                        {
 01949                            if (string.IsNullOrEmpty(qualifier))
 1950                            {
 01951                                if (!enableNonQualifiedConditions)
 1952                                {
 01953                                    continue;
 1954                                }
 1955                            }
 1956                            else
 1957                            {
 01958                                if (!enableQualifiedConditions)
 1959                                {
 1960                                    continue;
 1961                                }
 1962                            }
 1963
 01964                            if (int.TryParse(value, CultureInfo.InvariantCulture, out var num))
 1965                            {
 01966                                if (condition.Condition == ProfileConditionType.Equals)
 1967                                {
 01968                                    item.SetOption(qualifier, "videobitdepth", num.ToString(CultureInfo.InvariantCulture
 1969                                }
 01970                                else if (condition.Condition == ProfileConditionType.LessThanEqual)
 1971                                {
 01972                                    item.SetOption(qualifier, "videobitdepth", Math.Min(num, item.GetTargetVideoBitDepth
 1973                                }
 01974                                else if (condition.Condition == ProfileConditionType.GreaterThanEqual)
 1975                                {
 01976                                    item.SetOption(qualifier, "videobitdepth", Math.Max(num, item.GetTargetVideoBitDepth
 1977                                }
 1978                            }
 1979
 01980                            break;
 1981                        }
 1982
 1983                    case ProfileConditionValue.VideoProfile:
 1984                        {
 2121985                            if (string.IsNullOrEmpty(qualifier))
 1986                            {
 1987                                continue;
 1988                            }
 1989
 1990                            // Change from split by | to comma
 1991                            // Strip spaces to avoid having to encode
 2121992                            var values = value
 2121993                                .Split('|', StringSplitOptions.RemoveEmptyEntries);
 1994
 2121995                            if (condition.Condition == ProfileConditionType.Equals)
 1996                            {
 01997                                item.SetOption(qualifier, "profile", string.Join(',', values));
 1998                            }
 2121999                            else if (condition.Condition == ProfileConditionType.EqualsAny)
 2000                            {
 2122001                                var currentValue = item.GetOption(qualifier, "profile");
 2122002                                if (!string.IsNullOrEmpty(currentValue) && values.Any(value => value == currentValue))
 2003                                {
 652004                                    item.SetOption(qualifier, "profile", currentValue);
 2005                                }
 2006                                else
 2007                                {
 1472008                                    item.SetOption(qualifier, "profile", string.Join(',', values));
 2009                                }
 2010                            }
 2011
 1472012                            break;
 2013                        }
 2014
 2015                    case ProfileConditionValue.VideoRangeType:
 2016                        {
 2222017                            if (string.IsNullOrEmpty(qualifier))
 2018                            {
 2019                                continue;
 2020                            }
 2021
 2022                            // change from split by | to comma
 2023                            // strip spaces to avoid having to encode
 2222024                            var values = value
 2222025                                .Split('|', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
 2026
 2222027                            if (condition.Condition == ProfileConditionType.Equals)
 2028                            {
 02029                                item.SetOption(qualifier, "rangetype", string.Join(',', values));
 2030                            }
 2222031                            else if (condition.Condition == ProfileConditionType.NotEquals)
 2032                            {
 02033                                item.SetOption(qualifier, "rangetype", string.Join(',', Enum.GetNames(typeof(VideoRangeT
 2034                            }
 2222035                            else if (condition.Condition == ProfileConditionType.EqualsAny)
 2036                            {
 2222037                                var currentValue = item.GetOption(qualifier, "rangetype");
 2222038                                if (!string.IsNullOrEmpty(currentValue) && values.Any(v => string.Equals(v, currentValue
 2039                                {
 02040                                    item.SetOption(qualifier, "rangetype", currentValue);
 2041                                }
 2042                                else
 2043                                {
 2222044                                    item.SetOption(qualifier, "rangetype", string.Join(',', values));
 2045                                }
 2046                            }
 2047
 2222048                            break;
 2049                        }
 2050
 2051                    case ProfileConditionValue.VideoCodecTag:
 2052                        {
 122053                            if (string.IsNullOrEmpty(qualifier))
 2054                            {
 2055                                continue;
 2056                            }
 2057
 2058                            // change from split by | to comma
 2059                            // strip spaces to avoid having to encode
 122060                            var values = value
 122061                                .Split('|', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
 2062
 122063                            if (condition.Condition == ProfileConditionType.Equals)
 2064                            {
 02065                                item.SetOption(qualifier, "codectag", string.Join(',', values));
 2066                            }
 122067                            else if (condition.Condition == ProfileConditionType.EqualsAny)
 2068                            {
 122069                                var currentValue = item.GetOption(qualifier, "codectag");
 122070                                if (!string.IsNullOrEmpty(currentValue) && values.Any(v => string.Equals(v, currentValue
 2071                                {
 02072                                    item.SetOption(qualifier, "codectag", currentValue);
 2073                                }
 2074                                else
 2075                                {
 122076                                    item.SetOption(qualifier, "codectag", string.Join(',', values));
 2077                                }
 2078                            }
 2079
 122080                            break;
 2081                        }
 2082
 2083                    case ProfileConditionValue.Height:
 2084                        {
 02085                            if (!enableNonQualifiedConditions)
 2086                            {
 2087                                continue;
 2088                            }
 2089
 02090                            if (int.TryParse(value, CultureInfo.InvariantCulture, out var num))
 2091                            {
 02092                                if (condition.Condition == ProfileConditionType.Equals)
 2093                                {
 02094                                    item.MaxHeight = num;
 2095                                }
 02096                                else if (condition.Condition == ProfileConditionType.LessThanEqual)
 2097                                {
 02098                                    item.MaxHeight = Math.Min(num, item.MaxHeight ?? num);
 2099                                }
 02100                                else if (condition.Condition == ProfileConditionType.GreaterThanEqual)
 2101                                {
 02102                                    item.MaxHeight = Math.Max(num, item.MaxHeight ?? num);
 2103                                }
 2104                            }
 2105
 02106                            break;
 2107                        }
 2108
 2109                    case ProfileConditionValue.VideoBitrate:
 2110                        {
 202111                            if (!enableNonQualifiedConditions)
 2112                            {
 2113                                continue;
 2114                            }
 2115
 202116                            if (int.TryParse(value, CultureInfo.InvariantCulture, out var num))
 2117                            {
 202118                                if (condition.Condition == ProfileConditionType.Equals)
 2119                                {
 02120                                    item.VideoBitrate = num;
 2121                                }
 202122                                else if (condition.Condition == ProfileConditionType.LessThanEqual)
 2123                                {
 202124                                    item.VideoBitrate = Math.Min(num, item.VideoBitrate ?? num);
 2125                                }
 02126                                else if (condition.Condition == ProfileConditionType.GreaterThanEqual)
 2127                                {
 02128                                    item.VideoBitrate = Math.Max(num, item.VideoBitrate ?? num);
 2129                                }
 2130                            }
 2131
 02132                            break;
 2133                        }
 2134
 2135                    case ProfileConditionValue.VideoFramerate:
 2136                        {
 122137                            if (!enableNonQualifiedConditions)
 2138                            {
 2139                                continue;
 2140                            }
 2141
 122142                            if (float.TryParse(value, CultureInfo.InvariantCulture, out var num))
 2143                            {
 122144                                if (condition.Condition == ProfileConditionType.Equals)
 2145                                {
 02146                                    item.MaxFramerate = num;
 2147                                }
 122148                                else if (condition.Condition == ProfileConditionType.LessThanEqual)
 2149                                {
 122150                                    item.MaxFramerate = Math.Min(num, item.MaxFramerate ?? num);
 2151                                }
 02152                                else if (condition.Condition == ProfileConditionType.GreaterThanEqual)
 2153                                {
 02154                                    item.MaxFramerate = Math.Max(num, item.MaxFramerate ?? num);
 2155                                }
 2156                            }
 2157
 02158                            break;
 2159                        }
 2160
 2161                    case ProfileConditionValue.VideoLevel:
 2162                        {
 2002163                            if (string.IsNullOrEmpty(qualifier))
 2164                            {
 2165                                continue;
 2166                            }
 2167
 2002168                            if (int.TryParse(value, CultureInfo.InvariantCulture, out var num))
 2169                            {
 2002170                                if (condition.Condition == ProfileConditionType.Equals)
 2171                                {
 02172                                    item.SetOption(qualifier, "level", num.ToString(CultureInfo.InvariantCulture));
 2173                                }
 2002174                                else if (condition.Condition == ProfileConditionType.LessThanEqual)
 2175                                {
 2002176                                    item.SetOption(qualifier, "level", Math.Min(num, item.GetTargetVideoLevel(qualifier)
 2177                                }
 02178                                else if (condition.Condition == ProfileConditionType.GreaterThanEqual)
 2179                                {
 02180                                    item.SetOption(qualifier, "level", Math.Max(num, item.GetTargetVideoLevel(qualifier)
 2181                                }
 2182                            }
 2183
 02184                            break;
 2185                        }
 2186
 2187                    case ProfileConditionValue.Width:
 2188                        {
 02189                            if (!enableNonQualifiedConditions)
 2190                            {
 2191                                continue;
 2192                            }
 2193
 02194                            if (int.TryParse(value, CultureInfo.InvariantCulture, out var num))
 2195                            {
 02196                                if (condition.Condition == ProfileConditionType.Equals)
 2197                                {
 02198                                    item.MaxWidth = num;
 2199                                }
 02200                                else if (condition.Condition == ProfileConditionType.LessThanEqual)
 2201                                {
 02202                                    item.MaxWidth = Math.Min(num, item.MaxWidth ?? num);
 2203                                }
 02204                                else if (condition.Condition == ProfileConditionType.GreaterThanEqual)
 2205                                {
 02206                                    item.MaxWidth = Math.Max(num, item.MaxWidth ?? num);
 2207                                }
 2208                            }
 2209
 2210                            break;
 2211                        }
 2212
 2213                    default:
 2214                        break;
 2215                }
 2216            }
 4742217        }
 2218
 2219        private static bool IsAudioDirectPlaySupported(DirectPlayProfile profile, MediaSourceInfo item, MediaStream audi
 2220        {
 2221            // Check container type
 02222            if (!profile.SupportsContainer(item.Container))
 2223            {
 02224                return false;
 2225            }
 2226
 2227            // Check audio codec
 02228            string? audioCodec = audioStream?.Codec;
 02229            if (!profile.SupportsAudioCodec(audioCodec))
 2230            {
 02231                return false;
 2232            }
 2233
 02234            return true;
 2235        }
 2236
 2237        private static bool IsAudioDirectStreamSupported(DirectPlayProfile profile, MediaSourceInfo item, MediaStream au
 2238        {
 2239            // Check container type, this should NOT be supported
 2240            // If the container is supported, the file should be directly played
 02241            if (!profile.SupportsContainer(item.Container))
 2242            {
 2243                // Check audio codec, we cannot use the SupportsAudioCodec here
 2244                // Because that one assumes empty container supports all codec, which is just useless
 02245                string? audioCodec = audioStream?.Codec;
 02246                if (string.Equals(profile.AudioCodec, audioCodec, StringComparison.OrdinalIgnoreCase) ||
 02247                    string.Equals(profile.Container, audioCodec, StringComparison.OrdinalIgnoreCase))
 2248                {
 02249                    return true;
 2250                }
 2251            }
 2252
 02253            return false;
 2254        }
 2255
 2256        private int GetRank(ref TranscodeReason a, TranscodeReason[] rankings)
 2257        {
 10792258            var index = 1;
 78422259            foreach (var flag in rankings)
 2260            {
 32922261                var reason = a & flag;
 32922262                if (reason != 0)
 2263                {
 9002264                    return index;
 2265                }
 2266
 23922267                index++;
 2268            }
 2269
 1792270            return index;
 2271        }
 2272    }
 2273}

Methods/Properties

.cctor()
.ctor(MediaBrowser.Model.Dlna.ITranscoderSupport,Microsoft.Extensions.Logging.ILogger)
GetOptimalAudioStream(MediaBrowser.Model.Dlna.MediaOptions)
GetOptimalAudioStream(MediaBrowser.Model.Dto.MediaSourceInfo,MediaBrowser.Model.Dlna.MediaOptions)
GetOptimalVideoStream(MediaBrowser.Model.Dlna.MediaOptions)
GetOptimalStream(System.Collections.Generic.List`1<MediaBrowser.Model.Dlna.StreamInfo>,System.Int64)
SortMediaSources(System.Collections.Generic.List`1<MediaBrowser.Model.Dlna.StreamInfo>,System.Int64)
GetTranscodeReasonForFailedCondition(MediaBrowser.Model.Dlna.ProfileCondition)
NormalizeMediaSourceFormatIntoSingleContainer(System.String,MediaBrowser.Model.Dlna.DeviceProfile,MediaBrowser.Model.Dlna.DlnaProfileType,MediaBrowser.Model.Dlna.DirectPlayProfile)
GetAudioDirectPlayProfile(MediaBrowser.Model.Dto.MediaSourceInfo,MediaBrowser.Model.Entities.MediaStream,MediaBrowser.Model.Dlna.MediaOptions)
GetTranscodeReasonsFromDirectPlayProfile(MediaBrowser.Model.Dto.MediaSourceInfo,MediaBrowser.Model.Entities.MediaStream,MediaBrowser.Model.Entities.MediaStream,System.Collections.Generic.IEnumerable`1<MediaBrowser.Model.Dlna.DirectPlayProfile>)
GetDefaultSubtitleStreamIndex(MediaBrowser.Model.Dto.MediaSourceInfo,MediaBrowser.Model.Dlna.SubtitleProfile[])
SetStreamInfoOptionsFromTranscodingProfile(MediaBrowser.Model.Dto.MediaSourceInfo,MediaBrowser.Model.Dlna.StreamInfo,MediaBrowser.Model.Dlna.TranscodingProfile)
SetStreamInfoOptionsFromDirectPlayProfile(MediaBrowser.Model.Dlna.MediaOptions,MediaBrowser.Model.Dto.MediaSourceInfo,MediaBrowser.Model.Dlna.StreamInfo,MediaBrowser.Model.Dlna.DirectPlayProfile)
BuildVideoItem(MediaBrowser.Model.Dto.MediaSourceInfo,MediaBrowser.Model.Dlna.MediaOptions)
GetVideoTranscodeProfile(MediaBrowser.Model.Dto.MediaSourceInfo,MediaBrowser.Model.Dlna.MediaOptions,MediaBrowser.Model.Entities.MediaStream,MediaBrowser.Model.Entities.MediaStream,MediaBrowser.Model.Dlna.StreamInfo)
BuildStreamVideoItem(MediaBrowser.Model.Dlna.StreamInfo,MediaBrowser.Model.Dlna.MediaOptions,MediaBrowser.Model.Dto.MediaSourceInfo,MediaBrowser.Model.Entities.MediaStream,MediaBrowser.Model.Entities.MediaStream,System.Collections.Generic.IEnumerable`1<MediaBrowser.Model.Entities.MediaStream>,System.String,System.String,System.String)
GetDefaultAudioBitrate(System.String,System.Nullable`1<System.Int32>)
GetAudioBitrate(System.Int64,System.Collections.Generic.IReadOnlyList`1<System.String>,MediaBrowser.Model.Entities.MediaStream,MediaBrowser.Model.Dlna.StreamInfo)
GetMaxAudioBitrateForTotalBitrate(System.Int64)
GetVideoDirectPlayProfile(MediaBrowser.Model.Dlna.MediaOptions,MediaBrowser.Model.Dto.MediaSourceInfo,MediaBrowser.Model.Entities.MediaStream,MediaBrowser.Model.Entities.MediaStream,System.Collections.Generic.ICollection`1<MediaBrowser.Model.Entities.MediaStream>,MediaBrowser.Model.Entities.MediaStream,System.Boolean,System.Boolean)
CheckVideoAudioStreamDirectPlay(MediaBrowser.Model.Dlna.MediaOptions,MediaBrowser.Model.Dto.MediaSourceInfo,System.String,MediaBrowser.Model.Entities.MediaStream)
AggregateFailureConditions(MediaBrowser.Model.Dto.MediaSourceInfo,MediaBrowser.Model.Dlna.DeviceProfile,System.String,System.Collections.Generic.IEnumerable`1<MediaBrowser.Model.Dlna.ProfileCondition>)
LogConditionFailure(MediaBrowser.Model.Dlna.DeviceProfile,System.String,MediaBrowser.Model.Dlna.ProfileCondition,MediaBrowser.Model.Dto.MediaSourceInfo)
GetSubtitleProfile(MediaBrowser.Model.Dto.MediaSourceInfo,MediaBrowser.Model.Entities.MediaStream,MediaBrowser.Model.Dlna.SubtitleProfile[],MediaBrowser.Model.Session.PlayMethod,MediaBrowser.Model.Dlna.ITranscoderSupport,System.String,System.Nullable`1<Jellyfin.Data.Enums.MediaStreamProtocol>)
IsSubtitleEmbedSupported(System.String)
GetExternalSubtitleProfile(MediaBrowser.Model.Dto.MediaSourceInfo,MediaBrowser.Model.Entities.MediaStream,MediaBrowser.Model.Dlna.SubtitleProfile[],MediaBrowser.Model.Session.PlayMethod,MediaBrowser.Model.Dlna.ITranscoderSupport,System.Boolean)
IsBitrateLimitExceeded(MediaBrowser.Model.Dto.MediaSourceInfo,System.Int64)
ValidateMediaOptions(MediaBrowser.Model.Dlna.MediaOptions,System.Boolean)
GetProfileConditionsForVideoAudio(System.Collections.Generic.IEnumerable`1<MediaBrowser.Model.Dlna.CodecProfile>,System.String,System.String,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.String,System.Nullable`1<System.Boolean>)
GetProfileConditionsForAudio(System.Collections.Generic.IEnumerable`1<MediaBrowser.Model.Dlna.CodecProfile>,System.String,System.String,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Boolean)
ApplyTranscodingConditions(MediaBrowser.Model.Dlna.StreamInfo,System.Collections.Generic.IEnumerable`1<MediaBrowser.Model.Dlna.ProfileCondition>,System.String,System.Boolean,System.Boolean)
IsAudioDirectPlaySupported(MediaBrowser.Model.Dlna.DirectPlayProfile,MediaBrowser.Model.Dto.MediaSourceInfo,MediaBrowser.Model.Entities.MediaStream)
IsAudioDirectStreamSupported(MediaBrowser.Model.Dlna.DirectPlayProfile,MediaBrowser.Model.Dto.MediaSourceInfo,MediaBrowser.Model.Entities.MediaStream)
GetRank(MediaBrowser.Model.Session.TranscodeReason&,MediaBrowser.Model.Session.TranscodeReason[])