diff options
Diffstat (limited to 'MediaBrowser.Model/Dlna/StreamBuilder.cs')
| -rw-r--r-- | MediaBrowser.Model/Dlna/StreamBuilder.cs | 303 |
1 files changed, 171 insertions, 132 deletions
diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 7f387bfaae..a25ddc367d 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -6,6 +6,7 @@ using Jellyfin.Data.Enums; using Jellyfin.Extensions; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Extensions; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Session; using Microsoft.Extensions.Logging; @@ -19,15 +20,17 @@ namespace MediaBrowser.Model.Dlna { // Aliases internal const TranscodeReason ContainerReasons = TranscodeReason.ContainerNotSupported | TranscodeReason.ContainerBitrateExceedsLimit; - internal const TranscodeReason AudioReasons = TranscodeReason.AudioCodecNotSupported | TranscodeReason.AudioBitrateNotSupported | TranscodeReason.AudioChannelsNotSupported | TranscodeReason.AudioProfileNotSupported | TranscodeReason.AudioSampleRateNotSupported | TranscodeReason.SecondaryAudioNotSupported | TranscodeReason.AudioBitDepthNotSupported | TranscodeReason.AudioIsExternal; - internal const TranscodeReason VideoReasons = TranscodeReason.VideoCodecNotSupported | TranscodeReason.VideoResolutionNotSupported | TranscodeReason.AnamorphicVideoNotSupported | TranscodeReason.InterlacedVideoNotSupported | TranscodeReason.VideoBitDepthNotSupported | TranscodeReason.VideoBitrateNotSupported | TranscodeReason.VideoFramerateNotSupported | TranscodeReason.VideoLevelNotSupported | TranscodeReason.RefFramesNotSupported; - internal const TranscodeReason DirectStreamReasons = AudioReasons | TranscodeReason.ContainerNotSupported; + internal const TranscodeReason AudioCodecReasons = TranscodeReason.AudioBitrateNotSupported | TranscodeReason.AudioChannelsNotSupported | TranscodeReason.AudioProfileNotSupported | TranscodeReason.AudioSampleRateNotSupported | TranscodeReason.SecondaryAudioNotSupported | TranscodeReason.AudioBitDepthNotSupported | TranscodeReason.AudioIsExternal; + internal const TranscodeReason AudioReasons = TranscodeReason.AudioCodecNotSupported | AudioCodecReasons; + internal const TranscodeReason VideoCodecReasons = TranscodeReason.VideoResolutionNotSupported | TranscodeReason.AnamorphicVideoNotSupported | TranscodeReason.InterlacedVideoNotSupported | TranscodeReason.VideoBitDepthNotSupported | TranscodeReason.VideoBitrateNotSupported | TranscodeReason.VideoFramerateNotSupported | TranscodeReason.VideoLevelNotSupported | TranscodeReason.RefFramesNotSupported | TranscodeReason.VideoRangeTypeNotSupported | TranscodeReason.VideoProfileNotSupported; + internal const TranscodeReason VideoReasons = TranscodeReason.VideoCodecNotSupported | VideoCodecReasons; + internal const TranscodeReason DirectStreamReasons = AudioReasons | TranscodeReason.ContainerNotSupported | TranscodeReason.VideoCodecTagNotSupported; private readonly ILogger _logger; private readonly ITranscoderSupport _transcoderSupport; - private static readonly string[] _supportedHlsVideoCodecs = new string[] { "h264", "hevc", "vp9", "av1" }; - private static readonly string[] _supportedHlsAudioCodecsTs = new string[] { "aac", "ac3", "eac3", "mp3" }; - private static readonly string[] _supportedHlsAudioCodecsMp4 = new string[] { "aac", "ac3", "eac3", "mp3", "alac", "flac", "opus", "dca", "truehd" }; + private static readonly string[] _supportedHlsVideoCodecs = ["h264", "hevc", "vp9", "av1"]; + private static readonly string[] _supportedHlsAudioCodecsTs = ["aac", "ac3", "eac3", "mp3"]; + private static readonly string[] _supportedHlsAudioCodecsMp4 = ["aac", "ac3", "eac3", "mp3", "alac", "flac", "opus", "dca", "truehd"]; /// <summary> /// Initializes a new instance of the <see cref="StreamBuilder"/> class. @@ -49,7 +52,7 @@ namespace MediaBrowser.Model.Dlna { ValidateMediaOptions(options, false); - var streams = new List<StreamInfo>(); + List<StreamInfo> streams = []; foreach (var mediaSource in options.MediaSources) { if (!(string.IsNullOrEmpty(options.MediaSourceId) @@ -62,7 +65,7 @@ namespace MediaBrowser.Model.Dlna if (streamInfo is not null) { streamInfo.DeviceId = options.DeviceId; - streamInfo.DeviceProfileId = options.Profile.Id; + streamInfo.DeviceProfileId = options.Profile.Id?.ToString("N", CultureInfo.InvariantCulture); streams.Add(streamInfo); } } @@ -127,7 +130,7 @@ namespace MediaBrowser.Model.Dlna if (directPlayMethod is PlayMethod.DirectStream) { var remuxContainer = item.TranscodingContainer ?? "ts"; - var supportedHlsContainers = new[] { "ts", "mp4" }; + string[] supportedHlsContainers = ["ts", "mp4"]; // If the container specified for the profile is an HLS supported container, use that container instead, overriding the preference // The client should be responsible to ensure this container is compatible remuxContainer = Array.Exists(supportedHlsContainers, element => string.Equals(element, directPlayInfo.Profile?.Container, StringComparison.OrdinalIgnoreCase)) ? directPlayInfo.Profile?.Container : remuxContainer; @@ -224,7 +227,7 @@ namespace MediaBrowser.Model.Dlna ? options.MediaSources : options.MediaSources.Where(x => string.Equals(x.Id, options.MediaSourceId, StringComparison.OrdinalIgnoreCase)); - var streams = new List<StreamInfo>(); + List<StreamInfo> streams = []; foreach (var mediaSourceInfo in mediaSources) { var streamInfo = BuildVideoItem(mediaSourceInfo, options); @@ -237,7 +240,7 @@ namespace MediaBrowser.Model.Dlna foreach (var stream in streams) { stream.DeviceId = options.DeviceId; - stream.DeviceProfileId = options.Profile.Id; + stream.DeviceProfileId = options.Profile.Id?.ToString("N", CultureInfo.InvariantCulture); } return GetOptimalStream(streams, options.GetMaxBitrate(false) ?? 0); @@ -352,7 +355,7 @@ namespace MediaBrowser.Model.Dlna return TranscodeReason.VideoBitrateNotSupported; case ProfileConditionValue.VideoCodecTag: - return TranscodeReason.VideoCodecNotSupported; + return TranscodeReason.VideoCodecTagNotSupported; case ProfileConditionValue.VideoFramerate: return TranscodeReason.VideoFramerateNotSupported; @@ -388,30 +391,31 @@ namespace MediaBrowser.Model.Dlna /// <returns>The normalized input container.</returns> public static string? NormalizeMediaSourceFormatIntoSingleContainer(string inputContainer, DeviceProfile? profile, DlnaProfileType type, DirectPlayProfile? playProfile = null) { - if (string.IsNullOrEmpty(inputContainer)) + // If the source is Live TV the inputContainer will be null until the mediasource is probed on first access + if (profile is null || string.IsNullOrEmpty(inputContainer) || !inputContainer.Contains(',', StringComparison.OrdinalIgnoreCase)) { - return null; + return inputContainer; } - var formats = ContainerProfile.SplitValue(inputContainer); - - if (profile is not null) + var formats = ContainerHelper.Split(inputContainer); + var playProfiles = playProfile is null ? profile.DirectPlayProfiles : [playProfile]; + foreach (var format in formats) { - var playProfiles = playProfile is null ? profile.DirectPlayProfiles : new[] { playProfile }; - foreach (var format in formats) + foreach (var directPlayProfile in playProfiles) { - foreach (var directPlayProfile in playProfiles) + if (directPlayProfile.Type != type) { - if (directPlayProfile.Type == type - && directPlayProfile.SupportsContainer(format)) - { - return format; - } + continue; + } + + if (directPlayProfile.SupportsContainer(format)) + { + return format; } } } - return formats[0]; + return inputContainer; } private (DirectPlayProfile? Profile, PlayMethod? PlayMethod, TranscodeReason TranscodeReasons) GetAudioDirectPlayProfile(MediaSourceInfo item, MediaStream audioStream, MediaOptions options) @@ -531,7 +535,6 @@ namespace MediaBrowser.Model.Dlna private static int? GetDefaultSubtitleStreamIndex(MediaSourceInfo item, SubtitleProfile[] subtitleProfiles) { int highestScore = -1; - foreach (var stream in item.MediaStreams) { if (stream.Type == MediaStreamType.Subtitle @@ -542,7 +545,7 @@ namespace MediaBrowser.Model.Dlna } } - var topStreams = new List<MediaStream>(); + List<MediaStream> topStreams = []; foreach (var stream in item.MediaStreams) { if (stream.Type == MediaStreamType.Subtitle && stream.Score.HasValue && stream.Score.Value == highestScore) @@ -621,8 +624,8 @@ namespace MediaBrowser.Model.Dlna playlistItem.Container = container; playlistItem.SubProtocol = protocol; - playlistItem.VideoCodecs = new[] { item.VideoStream.Codec }; - playlistItem.AudioCodecs = ContainerProfile.SplitValue(directPlayProfile?.AudioCodec); + playlistItem.VideoCodecs = [item.VideoStream.Codec]; + playlistItem.AudioCodecs = ContainerHelper.Split(directPlayProfile?.AudioCodec); } private StreamInfo BuildVideoItem(MediaSourceInfo item, MediaOptions options) @@ -637,7 +640,8 @@ namespace MediaBrowser.Model.Dlna RunTimeTicks = item.RunTimeTicks, Context = options.Context, DeviceProfile = options.Profile, - SubtitleStreamIndex = options.SubtitleStreamIndex ?? GetDefaultSubtitleStreamIndex(item, options.Profile.SubtitleProfiles) + SubtitleStreamIndex = options.SubtitleStreamIndex ?? GetDefaultSubtitleStreamIndex(item, options.Profile.SubtitleProfiles), + AlwaysBurnInSubtitleWhenTranscoding = options.AlwaysBurnInSubtitleWhenTranscoding }; var subtitleStream = playlistItem.SubtitleStreamIndex.HasValue ? item.GetMediaStream(MediaStreamType.Subtitle, playlistItem.SubtitleStreamIndex.Value) : null; @@ -649,7 +653,7 @@ namespace MediaBrowser.Model.Dlna } // Collect candidate audio streams - ICollection<MediaStream> candidateAudioStreams = audioStream is null ? Array.Empty<MediaStream>() : new[] { audioStream }; + ICollection<MediaStream> candidateAudioStreams = audioStream is null ? [] : [audioStream]; if (!options.AudioStreamIndex.HasValue || options.AudioStreamIndex < 0) { if (audioStream?.IsDefault == true) @@ -700,7 +704,8 @@ namespace MediaBrowser.Model.Dlna directPlayProfile = directPlayInfo.Profile; playlistItem.PlayMethod = directPlay.Value; playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Video, directPlayProfile); - playlistItem.VideoCodecs = new[] { videoStream.Codec }; + var videoCodec = videoStream?.Codec; + playlistItem.VideoCodecs = videoCodec is null ? [] : [videoCodec]; if (directPlay == PlayMethod.DirectPlay) { @@ -711,7 +716,7 @@ namespace MediaBrowser.Model.Dlna { playlistItem.AudioStreamIndex = audioStreamIndex; var audioCodec = item.GetMediaStream(MediaStreamType.Audio, audioStreamIndex.Value)?.Codec; - playlistItem.AudioCodecs = audioCodec is null ? Array.Empty<string>() : new[] { audioCodec }; + playlistItem.AudioCodecs = audioCodec is null ? [] : [audioCodec]; } } else if (directPlay == PlayMethod.DirectStream) @@ -719,7 +724,7 @@ namespace MediaBrowser.Model.Dlna playlistItem.AudioStreamIndex = audioStream?.Index; if (audioStream is not null) { - playlistItem.AudioCodecs = ContainerProfile.SplitValue(directPlayProfile?.AudioCodec); + playlistItem.AudioCodecs = ContainerHelper.Split(directPlayProfile?.AudioCodec); } SetStreamInfoOptionsFromDirectPlayProfile(options, item, playlistItem, directPlayProfile); @@ -751,8 +756,9 @@ namespace MediaBrowser.Model.Dlna { // Can't direct play, find the transcoding profile // If we do this for direct-stream we will overwrite the info - var transcodingProfile = GetVideoTranscodeProfile(item, options, videoStream, audioStream, candidateAudioStreams, subtitleStream, playlistItem); - if (transcodingProfile is not null) + var (transcodingProfile, playMethod) = GetVideoTranscodeProfile(item, options, videoStream, audioStream, playlistItem); + + if (transcodingProfile is not null && playMethod.HasValue) { SetStreamInfoOptionsFromTranscodingProfile(item, playlistItem, transcodingProfile); @@ -763,10 +769,9 @@ namespace MediaBrowser.Model.Dlna if (subtitleStream is not null) { var subtitleProfile = GetSubtitleProfile(item, subtitleStream, options.Profile.SubtitleProfiles, PlayMethod.Transcode, _transcoderSupport, transcodingProfile.Container, transcodingProfile.Protocol); - playlistItem.SubtitleDeliveryMethod = subtitleProfile.Method; playlistItem.SubtitleFormat = subtitleProfile.Format; - playlistItem.SubtitleCodecs = new[] { subtitleProfile.Format }; + playlistItem.SubtitleCodecs = [subtitleProfile.Format]; } if ((playlistItem.TranscodeReasons & (VideoReasons | TranscodeReason.ContainerBitrateExceedsLimit)) != 0) @@ -790,58 +795,94 @@ namespace MediaBrowser.Model.Dlna return playlistItem; } - private TranscodingProfile? GetVideoTranscodeProfile( + private (TranscodingProfile? Profile, PlayMethod? PlayMethod) GetVideoTranscodeProfile( MediaSourceInfo item, MediaOptions options, MediaStream? videoStream, MediaStream? audioStream, - IEnumerable<MediaStream> candidateAudioStreams, - MediaStream? subtitleStream, StreamInfo playlistItem) { if (!(item.SupportsTranscoding || item.SupportsDirectStream)) { - return null; + return (null, null); } var transcodingProfiles = options.Profile.TranscodingProfiles .Where(i => i.Type == playlistItem.MediaType && i.Context == options.Context); - if (options.AllowVideoStreamCopy) + if (item.UseMostCompatibleTranscodingProfile) { - // prefer direct copy profile - float videoFramerate = videoStream?.AverageFrameRate ?? videoStream?.RealFrameRate ?? 0; - TransportStreamTimestamp? timestamp = videoStream is null ? TransportStreamTimestamp.None : item.Timestamp; - int? numAudioStreams = item.GetStreamCount(MediaStreamType.Audio); - int? numVideoStreams = item.GetStreamCount(MediaStreamType.Video); + transcodingProfiles = transcodingProfiles.Where(i => string.Equals(i.Container, "ts", StringComparison.OrdinalIgnoreCase)); + } + + var videoCodec = videoStream?.Codec; + float videoFramerate = videoStream?.ReferenceFrameRate ?? 0; + TransportStreamTimestamp? timestamp = videoStream is null ? TransportStreamTimestamp.None : item.Timestamp; + int? numAudioStreams = item.GetStreamCount(MediaStreamType.Audio); + int? numVideoStreams = item.GetStreamCount(MediaStreamType.Video); + + var audioCodec = audioStream?.Codec; + var audioProfile = audioStream?.Profile; + var audioChannels = audioStream?.Channels; + var audioBitrate = audioStream?.BitRate; + var audioSampleRate = audioStream?.SampleRate; + var audioBitDepth = audioStream?.BitDepth; - transcodingProfiles = transcodingProfiles.ToLookup(transcodingProfile => + var analyzedProfiles = transcodingProfiles + .Select(transcodingProfile => { - var videoCodecs = ContainerProfile.SplitValue(transcodingProfile.VideoCodec); + var rank = (Video: 3, Audio: 3); + + var container = transcodingProfile.Container; + + if (options.AllowVideoStreamCopy) + { + if (ContainerHelper.ContainsContainer(transcodingProfile.VideoCodec, videoCodec)) + { + var appliedVideoConditions = options.Profile.CodecProfiles + .Where(i => i.Type == CodecType.Video && + i.ContainsAnyCodec(videoCodec, container) && + i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoConditionSatisfied(applyCondition, videoStream?.Width, videoStream?.Height, videoStream?.BitDepth, videoStream?.BitRate, videoStream?.Profile, videoStream?.VideoRangeType, videoStream?.Level, videoFramerate, videoStream?.PacketLength, timestamp, videoStream?.IsAnamorphic, videoStream?.IsInterlaced, videoStream?.RefFrames, numVideoStreams, numAudioStreams, videoStream?.CodecTag, videoStream?.IsAVC))) + .Select(i => + i.Conditions.All(condition => ConditionProcessor.IsVideoConditionSatisfied(condition, videoStream?.Width, videoStream?.Height, videoStream?.BitDepth, videoStream?.BitRate, videoStream?.Profile, videoStream?.VideoRangeType, videoStream?.Level, videoFramerate, videoStream?.PacketLength, timestamp, videoStream?.IsAnamorphic, videoStream?.IsInterlaced, videoStream?.RefFrames, numVideoStreams, numAudioStreams, videoStream?.CodecTag, videoStream?.IsAVC))); + + // An empty appliedVideoConditions means that the codec has no conditions for the current video stream + var conditionsSatisfied = appliedVideoConditions.All(satisfied => satisfied); + rank.Video = conditionsSatisfied ? 1 : 2; + } + } - if (ContainerProfile.ContainsContainer(videoCodecs, item.VideoStream?.Codec)) + if (options.AllowAudioStreamCopy) { - var videoCodec = videoStream?.Codec; - var container = transcodingProfile.Container; - var appliedVideoConditions = options.Profile.CodecProfiles - .Where(i => i.Type == CodecType.Video && - i.ContainsAnyCodec(videoCodec, container) && - i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoConditionSatisfied(applyCondition, videoStream?.Width, videoStream?.Height, videoStream?.BitDepth, videoStream?.BitRate, videoStream?.Profile, videoStream?.VideoRangeType, videoStream?.Level, videoFramerate, videoStream?.PacketLength, timestamp, videoStream?.IsAnamorphic, videoStream?.IsInterlaced, videoStream?.RefFrames, numVideoStreams, numAudioStreams, videoStream?.CodecTag, videoStream?.IsAVC))) - .Select(i => - i.Conditions.All(condition => ConditionProcessor.IsVideoConditionSatisfied(condition, videoStream?.Width, videoStream?.Height, videoStream?.BitDepth, videoStream?.BitRate, videoStream?.Profile, videoStream?.VideoRangeType, videoStream?.Level, videoFramerate, videoStream?.PacketLength, timestamp, videoStream?.IsAnamorphic, videoStream?.IsInterlaced, videoStream?.RefFrames, numVideoStreams, numAudioStreams, videoStream?.CodecTag, videoStream?.IsAVC))); - - // An empty appliedVideoConditions means that the codec has no conditions for the current video stream - var conditionsSatisfied = appliedVideoConditions.All(satisfied => satisfied); - return conditionsSatisfied ? 1 : 2; + if (ContainerHelper.ContainsContainer(transcodingProfile.AudioCodec, audioCodec)) + { + var appliedVideoConditions = options.Profile.CodecProfiles + .Where(i => i.Type == CodecType.VideoAudio && + i.ContainsAnyCodec(audioCodec, container) && + i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, audioBitrate, audioSampleRate, audioBitDepth, audioProfile, false))) + .Select(i => + i.Conditions.All(condition => ConditionProcessor.IsVideoAudioConditionSatisfied(condition, audioChannels, audioBitrate, audioSampleRate, audioBitDepth, audioProfile, false))); + + // An empty appliedVideoConditions means that the codec has no conditions for the current audio stream + var conditionsSatisfied = appliedVideoConditions.All(satisfied => satisfied); + rank.Audio = conditionsSatisfied ? 1 : 2; + } + } + + PlayMethod playMethod = PlayMethod.Transcode; + + if (rank.Video == 1) + { + playMethod = PlayMethod.DirectStream; } - return 3; + return (Profile: transcodingProfile, PlayMethod: playMethod, Rank: rank); }) - .OrderBy(lookup => lookup.Key) - .SelectMany(lookup => lookup); - } + .OrderBy(analysis => analysis.Rank); - return transcodingProfiles.FirstOrDefault(); + var profileMatch = analyzedProfiles.FirstOrDefault(); + + return (profileMatch.Profile, profileMatch.PlayMethod); } private void BuildStreamVideoItem( @@ -856,26 +897,24 @@ namespace MediaBrowser.Model.Dlna string? audioCodec) { // Prefer matching video codecs - var videoCodecs = ContainerProfile.SplitValue(videoCodec); + var videoCodecs = ContainerHelper.Split(videoCodec).ToList(); - // Enforce HLS video codec restrictions - if (playlistItem.SubProtocol == MediaStreamProtocol.hls) + if (videoCodecs.Count == 0 && videoStream is not null) { - videoCodecs = videoCodecs.Where(codec => _supportedHlsVideoCodecs.Contains(codec)).ToArray(); + // Add the original codec if no codec is specified + videoCodecs.Add(videoStream.Codec); } - var directVideoCodec = ContainerProfile.ContainsContainer(videoCodecs, videoStream?.Codec) ? videoStream?.Codec : null; - if (directVideoCodec is not null) + // Enforce HLS video codec restrictions + if (playlistItem.SubProtocol == MediaStreamProtocol.hls) { - // merge directVideoCodec to videoCodecs - Array.Resize(ref videoCodecs, videoCodecs.Length + 1); - videoCodecs[^1] = directVideoCodec; + videoCodecs = videoCodecs.Where(codec => _supportedHlsVideoCodecs.Contains(codec)).ToList(); } playlistItem.VideoCodecs = videoCodecs; // Copy video codec options as a starting point, this applies to transcode and direct-stream - playlistItem.MaxFramerate = videoStream?.AverageFrameRate; + playlistItem.MaxFramerate = videoStream?.ReferenceFrameRate; var qualifier = videoStream?.Codec; if (videoStream?.Level is not null) { @@ -893,22 +932,28 @@ namespace MediaBrowser.Model.Dlna } // Prefer matching audio codecs, could do better here - var audioCodecs = ContainerProfile.SplitValue(audioCodec); + var audioCodecs = ContainerHelper.Split(audioCodec).ToList(); + + if (audioCodecs.Count == 0 && audioStream is not null) + { + // Add the original codec if no codec is specified + audioCodecs.Add(audioStream.Codec); + } // Enforce HLS audio codec restrictions if (playlistItem.SubProtocol == MediaStreamProtocol.hls) { if (string.Equals(playlistItem.Container, "mp4", StringComparison.OrdinalIgnoreCase)) { - audioCodecs = audioCodecs.Where(codec => _supportedHlsAudioCodecsMp4.Contains(codec)).ToArray(); + audioCodecs = audioCodecs.Where(codec => _supportedHlsAudioCodecsMp4.Contains(codec)).ToList(); } else { - audioCodecs = audioCodecs.Where(codec => _supportedHlsAudioCodecsTs.Contains(codec)).ToArray(); + audioCodecs = audioCodecs.Where(codec => _supportedHlsAudioCodecsTs.Contains(codec)).ToList(); } } - var audioStreamWithSupportedCodec = candidateAudioStreams.Where(stream => ContainerProfile.ContainsContainer(audioCodecs, stream.Codec)).FirstOrDefault(); + var audioStreamWithSupportedCodec = candidateAudioStreams.Where(stream => ContainerHelper.ContainsContainer(audioCodecs, false, stream.Codec)).FirstOrDefault(); var directAudioStream = audioStreamWithSupportedCodec?.Channels is not null && audioStreamWithSupportedCodec.Channels.Value <= (playlistItem.TranscodingMaxAudioChannels ?? int.MaxValue) ? audioStreamWithSupportedCodec : null; @@ -925,7 +970,8 @@ namespace MediaBrowser.Model.Dlna { audioStream = directAudioStream; playlistItem.AudioStreamIndex = audioStream.Index; - playlistItem.AudioCodecs = new[] { audioStream.Codec }; + audioCodecs = [audioStream.Codec]; + playlistItem.AudioCodecs = audioCodecs; // Copy matching audio codec options playlistItem.AudioSampleRate = audioStream.SampleRate; @@ -949,7 +995,7 @@ namespace MediaBrowser.Model.Dlna double? videoLevel = videoStream?.Level; string? videoProfile = videoStream?.Profile; VideoRangeType? videoRangeType = videoStream?.VideoRangeType; - float videoFramerate = videoStream is null ? 0 : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate ?? 0; + float videoFramerate = videoStream is null ? 0 : videoStream.ReferenceFrameRate ?? 0; bool? isAnamorphic = videoStream?.IsAnamorphic; bool? isInterlaced = videoStream?.IsInterlaced; string? videoCodecTag = videoStream?.CodecTag; @@ -966,19 +1012,17 @@ namespace MediaBrowser.Model.Dlna var appliedVideoConditions = options.Profile.CodecProfiles .Where(i => i.Type == CodecType.Video && - i.ContainsAnyCodec(videoStream?.Codec, container, useSubContainer) && + i.ContainsAnyCodec(playlistItem.VideoCodecs, container, useSubContainer) && i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoRangeType, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc))) // Reverse codec profiles for backward compatibility - first codec profile has higher priority .Reverse(); - - foreach (var i in appliedVideoConditions) + foreach (var condition in appliedVideoConditions) { - var transcodingVideoCodecs = ContainerProfile.SplitValue(videoCodec); - foreach (var transcodingVideoCodec in transcodingVideoCodecs) + foreach (var transcodingVideoCodec in playlistItem.VideoCodecs) { - if (i.ContainsAnyCodec(transcodingVideoCodec, container, useSubContainer)) + if (condition.ContainsAnyCodec(transcodingVideoCodec, container, useSubContainer)) { - ApplyTranscodingConditions(playlistItem, i.Conditions, transcodingVideoCodec, true, true); + ApplyTranscodingConditions(playlistItem, condition.Conditions, transcodingVideoCodec, true, true); continue; } } @@ -999,15 +1043,14 @@ namespace MediaBrowser.Model.Dlna var appliedAudioConditions = options.Profile.CodecProfiles .Where(i => i.Type == CodecType.VideoAudio && - i.ContainsAnyCodec(audioStream?.Codec, container) && + i.ContainsAnyCodec(playlistItem.AudioCodecs, container) && i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth, audioProfile, isSecondaryAudio))) // Reverse codec profiles for backward compatibility - first codec profile has higher priority .Reverse(); foreach (var codecProfile in appliedAudioConditions) { - var transcodingAudioCodecs = ContainerProfile.SplitValue(audioCodec); - foreach (var transcodingAudioCodec in transcodingAudioCodecs) + foreach (var transcodingAudioCodec in playlistItem.AudioCodecs) { if (codecProfile.ContainsAnyCodec(transcodingAudioCodec, container)) { @@ -1077,9 +1120,9 @@ namespace MediaBrowser.Model.Dlna return 192000; } - private static int GetAudioBitrate(long maxTotalBitrate, string[] targetAudioCodecs, MediaStream? audioStream, StreamInfo item) + private static int GetAudioBitrate(long maxTotalBitrate, IReadOnlyList<string> targetAudioCodecs, MediaStream? audioStream, StreamInfo item) { - string? targetAudioCodec = targetAudioCodecs.Length == 0 ? null : targetAudioCodecs[0]; + string? targetAudioCodec = targetAudioCodecs.Count == 0 ? null : targetAudioCodecs[0]; int? targetAudioChannels = item.GetTargetAudioChannels(targetAudioCodec); @@ -1096,7 +1139,7 @@ namespace MediaBrowser.Model.Dlna && audioStream.Channels.HasValue && audioStream.Channels.Value > targetAudioChannels.Value) { - // Reduce the bitrate if we're downmixing. + // Reduce the bitrate if we're down mixing. defaultBitrate = GetDefaultAudioBitrate(targetAudioCodec, targetAudioChannels); } else if (targetAudioChannels.HasValue @@ -1104,8 +1147,8 @@ namespace MediaBrowser.Model.Dlna && audioStream.Channels.Value <= targetAudioChannels.Value && !string.IsNullOrEmpty(audioStream.Codec) && targetAudioCodecs is not null - && targetAudioCodecs.Length > 0 - && !Array.Exists(targetAudioCodecs, elem => string.Equals(audioStream.Codec, elem, StringComparison.OrdinalIgnoreCase))) + && targetAudioCodecs.Count > 0 + && !targetAudioCodecs.Any(elem => string.Equals(audioStream.Codec, elem, StringComparison.OrdinalIgnoreCase))) { // Shift the bitrate if we're transcoding to a different audio codec. defaultBitrate = GetDefaultAudioBitrate(targetAudioCodec, audioStream.Channels.Value); @@ -1208,7 +1251,7 @@ namespace MediaBrowser.Model.Dlna double? videoLevel = videoStream?.Level; string? videoProfile = videoStream?.Profile; VideoRangeType? videoRangeType = videoStream?.VideoRangeType; - float videoFramerate = videoStream is null ? 0 : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate ?? 0; + float videoFramerate = videoStream is null ? 0 : videoStream.ReferenceFrameRate ?? 0; bool? isAnamorphic = videoStream?.IsAnamorphic; bool? isInterlaced = videoStream?.IsInterlaced; string? videoCodecTag = videoStream?.CodecTag; @@ -1244,7 +1287,7 @@ namespace MediaBrowser.Model.Dlna !checkVideoConditions(codecProfile.ApplyConditions).Any()) .SelectMany(codecProfile => checkVideoConditions(codecProfile.Conditions))); - // Check audiocandidates profile conditions + // Check audio candidates profile conditions var audioStreamMatches = candidateAudioStreams.ToDictionary(s => s, audioStream => CheckVideoAudioStreamDirectPlay(options, mediaSource, container, audioStream)); TranscodeReason subtitleProfileReasons = 0; @@ -1261,25 +1304,8 @@ namespace MediaBrowser.Model.Dlna } } - var rankings = new[] { VideoReasons, AudioReasons, ContainerReasons }; - var rank = (ref TranscodeReason a) => - { - var index = 1; - foreach (var flag in rankings) - { - var reason = a & flag; - if (reason != 0) - { - return index; - } - - index++; - } - - return index; - }; - var containerSupported = false; + TranscodeReason[] rankings = [TranscodeReason.VideoCodecNotSupported, VideoCodecReasons, TranscodeReason.AudioCodecNotSupported, AudioCodecReasons, ContainerReasons]; // Check DirectPlay profiles to see if it can be direct played var analyzedProfiles = profile.DirectPlayProfiles @@ -1345,7 +1371,8 @@ namespace MediaBrowser.Model.Dlna playMethod = PlayMethod.DirectStream; } - var ranked = rank(ref failureReasons); + var ranked = GetRank(ref failureReasons, rankings); + return (Result: (Profile: directPlayProfile, PlayMethod: playMethod, AudioStreamIndex: selectedAudioStream?.Index, TranscodeReason: failureReasons), Order: order, Rank: ranked); }) .OrderByDescending(analysis => analysis.Result.PlayMethod) @@ -1364,7 +1391,7 @@ namespace MediaBrowser.Model.Dlna var failureReasons = analyzedProfiles[false] .Select(analysis => analysis.Result) - .Where(result => !containerSupported || (result.TranscodeReason & TranscodeReason.ContainerNotSupported) == 0) + .Where(result => !containerSupported || !result.TranscodeReason.HasFlag(TranscodeReason.ContainerNotSupported)) .FirstOrDefault().TranscodeReason; if (failureReasons == 0) { @@ -1420,7 +1447,7 @@ namespace MediaBrowser.Model.Dlna /// <param name="playMethod">The <see cref="PlayMethod"/>.</param> /// <param name="transcoderSupport">The <see cref="ITranscoderSupport"/>.</param> /// <param name="outputContainer">The output container.</param> - /// <param name="transcodingSubProtocol">The subtitle transoding protocol.</param> + /// <param name="transcodingSubProtocol">The subtitle transcoding protocol.</param> /// <returns>The normalized input container.</returns> public static SubtitleProfile GetSubtitleProfile( MediaSourceInfo mediaSource, @@ -1446,7 +1473,7 @@ namespace MediaBrowser.Model.Dlna continue; } - if (!ContainerProfile.ContainsContainer(profile.Container, outputContainer)) + if (!ContainerHelper.ContainsContainer(profile.Container, outputContainer)) { continue; } @@ -1475,7 +1502,7 @@ namespace MediaBrowser.Model.Dlna continue; } - if (!ContainerProfile.ContainsContainer(profile.Container, outputContainer)) + if (!ContainerHelper.ContainsContainer(profile.Container, outputContainer)) { continue; } @@ -1506,17 +1533,12 @@ namespace MediaBrowser.Model.Dlna { if (!string.IsNullOrEmpty(transcodingContainer)) { - string[] normalizedContainers = ContainerProfile.SplitValue(transcodingContainer); - - if (ContainerProfile.ContainsContainer(normalizedContainers, "ts") - || ContainerProfile.ContainsContainer(normalizedContainers, "mpegts") - || ContainerProfile.ContainsContainer(normalizedContainers, "mp4")) + if (ContainerHelper.ContainsContainer(transcodingContainer, "ts,mpegts,mp4")) { return false; } - if (ContainerProfile.ContainsContainer(normalizedContainers, "mkv") - || ContainerProfile.ContainsContainer(normalizedContainers, "matroska")) + if (ContainerHelper.ContainsContainer(transcodingContainer, "mkv,matroska")) { return true; } @@ -2219,5 +2241,22 @@ namespace MediaBrowser.Model.Dlna return false; } + + private int GetRank(ref TranscodeReason a, TranscodeReason[] rankings) + { + var index = 1; + foreach (var flag in rankings) + { + var reason = a & flag; + if (reason != 0) + { + return index; + } + + index++; + } + + return index; + } } } |
