aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Model/Dlna/StreamBuilder.cs
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.Model/Dlna/StreamBuilder.cs')
-rw-r--r--MediaBrowser.Model/Dlna/StreamBuilder.cs303
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;
+ }
}
}