jellyfin/MediaBrowser.Model/Dlna/StreamBuilder.cs
Erwin de Haan ec1f5dc317 Mayor code cleanup
Add Argument*Exceptions now use proper nameof operators.

Added exception messages to quite a few Argument*Exceptions.

Fixed rethorwing to be proper syntax.

Added a ton of null checkes. (This is only a start, there are about 500 places that need proper null handling)

Added some TODOs to log certain exceptions.

Fix sln again.

Fixed all AssemblyInfo's and added proper copyright (where I could find them)

We live in *current year*.

Fixed the use of braces.

Fixed a ton of properties, and made a fair amount of functions static that should be and can be static.

Made more Methods that should be static static.

You can now use static to find bad functions!

Removed unused variable. And added one more proper XML comment.
2019-01-10 20:38:53 +01:00

1901 lines
82 KiB
C#

using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Extensions;
using Microsoft.Extensions.Logging;
using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Session;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
namespace MediaBrowser.Model.Dlna
{
public class StreamBuilder
{
private readonly ILogger _logger;
private readonly ITranscoderSupport _transcoderSupport;
public StreamBuilder(ITranscoderSupport transcoderSupport, ILogger logger)
{
_transcoderSupport = transcoderSupport;
_logger = logger;
}
public StreamBuilder(ILogger logger)
: this(new FullTranscoderSupport(), logger)
{
}
public StreamInfo BuildAudioItem(AudioOptions options)
{
ValidateAudioInput(options);
var mediaSources = new List<MediaSourceInfo>();
foreach (MediaSourceInfo i in options.MediaSources)
{
if (string.IsNullOrEmpty(options.MediaSourceId) ||
StringHelper.EqualsIgnoreCase(i.Id, options.MediaSourceId))
{
mediaSources.Add(i);
}
}
var streams = new List<StreamInfo>();
foreach (MediaSourceInfo i in mediaSources)
{
StreamInfo streamInfo = BuildAudioItem(i, options);
if (streamInfo != null)
{
streams.Add(streamInfo);
}
}
foreach (StreamInfo stream in streams)
{
stream.DeviceId = options.DeviceId;
stream.DeviceProfileId = options.Profile.Id;
}
return GetOptimalStream(streams, options.GetMaxBitrate(true) ?? 0);
}
public StreamInfo BuildVideoItem(VideoOptions options)
{
ValidateInput(options);
var mediaSources = new List<MediaSourceInfo>();
foreach (MediaSourceInfo i in options.MediaSources)
{
if (string.IsNullOrEmpty(options.MediaSourceId) ||
StringHelper.EqualsIgnoreCase(i.Id, options.MediaSourceId))
{
mediaSources.Add(i);
}
}
var streams = new List<StreamInfo>();
foreach (MediaSourceInfo i in mediaSources)
{
StreamInfo streamInfo = BuildVideoItem(i, options);
if (streamInfo != null)
{
streams.Add(streamInfo);
}
}
foreach (StreamInfo stream in streams)
{
stream.DeviceId = options.DeviceId;
stream.DeviceProfileId = options.Profile.Id;
}
return GetOptimalStream(streams, options.GetMaxBitrate(false) ?? 0);
}
private StreamInfo GetOptimalStream(List<StreamInfo> streams, long maxBitrate)
{
var sorted = SortMediaSources(streams, maxBitrate);
foreach (StreamInfo stream in sorted)
{
return stream;
}
return null;
}
private StreamInfo[] SortMediaSources(List<StreamInfo> streams, long maxBitrate)
{
return streams.OrderBy(i =>
{
// Nothing beats direct playing a file
if (i.PlayMethod == PlayMethod.DirectPlay && i.MediaSource.Protocol == MediaProtocol.File)
{
return 0;
}
return 1;
}).ThenBy(i =>
{
switch (i.PlayMethod)
{
// Let's assume direct streaming a file is just as desirable as direct playing a remote url
case PlayMethod.DirectStream:
case PlayMethod.DirectPlay:
return 0;
default:
return 1;
}
}).ThenBy(i =>
{
switch (i.MediaSource.Protocol)
{
case MediaProtocol.File:
return 0;
default:
return 1;
}
}).ThenBy(i =>
{
if (maxBitrate > 0)
{
if (i.MediaSource.Bitrate.HasValue)
{
return Math.Abs(i.MediaSource.Bitrate.Value - maxBitrate);
}
}
return 0;
}).ThenBy(streams.IndexOf).ToArray();
}
private TranscodeReason? GetTranscodeReasonForFailedCondition(ProfileCondition condition)
{
switch (condition.Property)
{
case ProfileConditionValue.AudioBitrate:
if (condition.Condition == ProfileConditionType.LessThanEqual)
{
return TranscodeReason.AudioBitrateNotSupported;
}
return TranscodeReason.AudioBitrateNotSupported;
case ProfileConditionValue.AudioChannels:
if (condition.Condition == ProfileConditionType.LessThanEqual)
{
return TranscodeReason.AudioChannelsNotSupported;
}
return TranscodeReason.AudioChannelsNotSupported;
case ProfileConditionValue.AudioProfile:
return TranscodeReason.AudioProfileNotSupported;
case ProfileConditionValue.AudioSampleRate:
return TranscodeReason.AudioSampleRateNotSupported;
case ProfileConditionValue.Has64BitOffsets:
// TODO
return null;
case ProfileConditionValue.Height:
return TranscodeReason.VideoResolutionNotSupported;
case ProfileConditionValue.IsAnamorphic:
return TranscodeReason.AnamorphicVideoNotSupported;
case ProfileConditionValue.IsAvc:
// TODO
return null;
case ProfileConditionValue.IsInterlaced:
return TranscodeReason.InterlacedVideoNotSupported;
case ProfileConditionValue.IsSecondaryAudio:
return TranscodeReason.SecondaryAudioNotSupported;
case ProfileConditionValue.NumAudioStreams:
// TODO
return null;
case ProfileConditionValue.NumVideoStreams:
// TODO
return null;
case ProfileConditionValue.PacketLength:
// TODO
return null;
case ProfileConditionValue.RefFrames:
return TranscodeReason.RefFramesNotSupported;
case ProfileConditionValue.VideoBitDepth:
return TranscodeReason.VideoBitDepthNotSupported;
case ProfileConditionValue.AudioBitDepth:
return TranscodeReason.AudioBitDepthNotSupported;
case ProfileConditionValue.VideoBitrate:
return TranscodeReason.VideoBitrateNotSupported;
case ProfileConditionValue.VideoCodecTag:
return TranscodeReason.VideoCodecNotSupported;
case ProfileConditionValue.VideoFramerate:
return TranscodeReason.VideoFramerateNotSupported;
case ProfileConditionValue.VideoLevel:
return TranscodeReason.VideoLevelNotSupported;
case ProfileConditionValue.VideoProfile:
return TranscodeReason.VideoProfileNotSupported;
case ProfileConditionValue.VideoTimestamp:
// TODO
return null;
case ProfileConditionValue.Width:
return TranscodeReason.VideoResolutionNotSupported;
default:
return null;
}
}
public static string NormalizeMediaSourceFormatIntoSingleContainer(string inputContainer, string unused1, DeviceProfile profile, DlnaProfileType type)
{
if (string.IsNullOrEmpty(inputContainer))
{
return null;
}
var formats = ContainerProfile.SplitValue(inputContainer);
if (formats.Length == 1)
{
return formats[0];
}
if (profile != null)
{
foreach (var format in formats)
{
foreach (var directPlayProfile in profile.DirectPlayProfiles)
{
if (directPlayProfile.Type == type)
{
if (directPlayProfile.SupportsContainer(format))
{
return format;
}
}
}
}
}
return formats[0];
}
private StreamInfo BuildAudioItem(MediaSourceInfo item, AudioOptions options)
{
var transcodeReasons = new List<TranscodeReason>();
StreamInfo playlistItem = new StreamInfo
{
ItemId = options.ItemId,
MediaType = DlnaProfileType.Audio,
MediaSource = item,
RunTimeTicks = item.RunTimeTicks,
Context = options.Context,
DeviceProfile = options.Profile
};
if (options.ForceDirectPlay)
{
playlistItem.PlayMethod = PlayMethod.DirectPlay;
playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, item.Path, options.Profile, DlnaProfileType.Audio);
return playlistItem;
}
if (options.ForceDirectStream)
{
playlistItem.PlayMethod = PlayMethod.DirectStream;
playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, item.Path, options.Profile, DlnaProfileType.Audio);
return playlistItem;
}
MediaStream audioStream = item.GetDefaultAudioStream(null);
var directPlayInfo = GetAudioDirectPlayMethods(item, audioStream, options);
var directPlayMethods = directPlayInfo.Item1;
transcodeReasons.AddRange(directPlayInfo.Item2);
ConditionProcessor conditionProcessor = new ConditionProcessor();
int? inputAudioChannels = audioStream == null ? null : audioStream.Channels;
int? inputAudioBitrate = audioStream == null ? null : audioStream.BitDepth;
int? inputAudioSampleRate = audioStream == null ? null : audioStream.SampleRate;
int? inputAudioBitDepth = audioStream == null ? null : audioStream.BitDepth;
if (directPlayMethods.Count > 0)
{
string audioCodec = audioStream == null ? null : audioStream.Codec;
// Make sure audio codec profiles are satisfied
var conditions = new List<ProfileCondition>();
foreach (CodecProfile i in options.Profile.CodecProfiles)
{
if (i.Type == CodecType.Audio && i.ContainsAnyCodec(audioCodec, item.Container))
{
bool applyConditions = true;
foreach (ProfileCondition applyCondition in i.ApplyConditions)
{
if (!conditionProcessor.IsAudioConditionSatisfied(applyCondition, inputAudioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth))
{
LogConditionFailure(options.Profile, "AudioCodecProfile", applyCondition, item);
applyConditions = false;
break;
}
}
if (applyConditions)
{
foreach (ProfileCondition c in i.Conditions)
{
conditions.Add(c);
}
}
}
}
bool all = true;
foreach (ProfileCondition c in conditions)
{
if (!conditionProcessor.IsAudioConditionSatisfied(c, inputAudioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth))
{
LogConditionFailure(options.Profile, "AudioCodecProfile", c, item);
var transcodeReason = GetTranscodeReasonForFailedCondition(c);
if (transcodeReason.HasValue)
{
transcodeReasons.Add(transcodeReason.Value);
}
all = false;
break;
}
}
if (all)
{
if (directPlayMethods.Contains(PlayMethod.DirectStream))
{
playlistItem.PlayMethod = PlayMethod.DirectStream;
}
playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, item.Path, options.Profile, DlnaProfileType.Audio);
return playlistItem;
}
}
TranscodingProfile transcodingProfile = null;
foreach (TranscodingProfile i in options.Profile.TranscodingProfiles)
{
if (i.Type == playlistItem.MediaType && i.Context == options.Context)
{
if (_transcoderSupport.CanEncodeToAudioCodec(i.AudioCodec ?? i.Container))
{
transcodingProfile = i;
break;
}
}
}
if (transcodingProfile != null)
{
if (!item.SupportsTranscoding)
{
return null;
}
SetStreamInfoOptionsFromTranscodingProfile(playlistItem, transcodingProfile);
var audioCodecProfiles = new List<CodecProfile>();
foreach (CodecProfile i in options.Profile.CodecProfiles)
{
if (i.Type == CodecType.Audio && i.ContainsAnyCodec(transcodingProfile.AudioCodec, transcodingProfile.Container))
{
audioCodecProfiles.Add(i);
}
if (audioCodecProfiles.Count >= 1) break;
}
var audioTranscodingConditions = new List<ProfileCondition>();
foreach (CodecProfile i in audioCodecProfiles)
{
bool applyConditions = true;
foreach (ProfileCondition applyCondition in i.ApplyConditions)
{
if (!conditionProcessor.IsAudioConditionSatisfied(applyCondition, inputAudioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth))
{
LogConditionFailure(options.Profile, "AudioCodecProfile", applyCondition, item);
applyConditions = false;
break;
}
}
if (applyConditions)
{
foreach (ProfileCondition c in i.Conditions)
{
audioTranscodingConditions.Add(c);
}
}
}
ApplyTranscodingConditions(playlistItem, audioTranscodingConditions, null, true, true);
// Honor requested max channels
playlistItem.GlobalMaxAudioChannels = options.MaxAudioChannels;
var configuredBitrate = options.GetMaxBitrate(true);
long transcodingBitrate = options.AudioTranscodingBitrate ??
(options.Context == EncodingContext.Streaming ? options.Profile.MusicStreamingTranscodingBitrate : null) ??
configuredBitrate ??
128000;
if (configuredBitrate.HasValue)
{
transcodingBitrate = Math.Min(configuredBitrate.Value, transcodingBitrate);
}
var longBitrate = Math.Min(transcodingBitrate, playlistItem.AudioBitrate ?? transcodingBitrate);
playlistItem.AudioBitrate = longBitrate > int.MaxValue ? int.MaxValue : Convert.ToInt32(longBitrate);
}
playlistItem.TranscodeReasons = transcodeReasons.ToArray();
return playlistItem;
}
private long? GetBitrateForDirectPlayCheck(MediaSourceInfo item, AudioOptions options, bool isAudio)
{
if (item.Protocol == MediaProtocol.File)
{
return options.Profile.MaxStaticBitrate;
}
return options.GetMaxBitrate(isAudio);
}
private Tuple<List<PlayMethod>, List<TranscodeReason>> GetAudioDirectPlayMethods(MediaSourceInfo item, MediaStream audioStream, AudioOptions options)
{
var transcodeReasons = new List<TranscodeReason>();
DirectPlayProfile directPlayProfile = null;
foreach (DirectPlayProfile i in options.Profile.DirectPlayProfiles)
{
if (i.Type == DlnaProfileType.Audio && IsAudioDirectPlaySupported(i, item, audioStream))
{
directPlayProfile = i;
break;
}
}
var playMethods = new List<PlayMethod>();
if (directPlayProfile != null)
{
// While options takes the network and other factors into account. Only applies to direct stream
if (item.SupportsDirectStream)
{
if (IsAudioEligibleForDirectPlay(item, options.GetMaxBitrate(true) ?? 0, PlayMethod.DirectStream))
{
if (options.EnableDirectStream)
{
playMethods.Add(PlayMethod.DirectStream);
}
}
else
{
transcodeReasons.Add(TranscodeReason.ContainerBitrateExceedsLimit);
}
}
// The profile describes what the device supports
// If device requirements are satisfied then allow both direct stream and direct play
if (item.SupportsDirectPlay)
{
if (IsAudioEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options, true) ?? 0, PlayMethod.DirectPlay))
{
if (options.EnableDirectPlay)
{
playMethods.Add(PlayMethod.DirectPlay);
}
}
else
{
transcodeReasons.Add(TranscodeReason.ContainerBitrateExceedsLimit);
}
}
}
else
{
transcodeReasons.InsertRange(0, GetTranscodeReasonsFromDirectPlayProfile(item, null, audioStream, options.Profile.DirectPlayProfiles));
_logger.LogInformation("Profile: {0}, No direct play profiles found for Path: {1}",
options.Profile.Name ?? "Unknown Profile",
item.Path ?? "Unknown path");
}
if (playMethods.Count > 0)
{
transcodeReasons.Clear();
}
else
{
transcodeReasons = transcodeReasons.Distinct().ToList();
}
return new Tuple<List<PlayMethod>, List<TranscodeReason>>(playMethods, transcodeReasons);
}
private List<TranscodeReason> GetTranscodeReasonsFromDirectPlayProfile(MediaSourceInfo item, MediaStream videoStream, MediaStream audioStream, IEnumerable<DirectPlayProfile> directPlayProfiles)
{
var list = new List<TranscodeReason>();
var containerSupported = false;
var audioSupported = false;
var videoSupported = false;
foreach (var profile in directPlayProfiles)
{
audioSupported = false;
videoSupported = false;
// Check container type
if (profile.SupportsContainer(item.Container))
{
containerSupported = true;
if (videoStream != null)
{
if (profile.SupportsVideoCodec(videoStream.Codec))
{
videoSupported = true;
}
}
if (audioStream != null)
{
if (profile.SupportsAudioCodec(audioStream.Codec))
{
audioSupported = true;
}
}
if (videoSupported && audioSupported)
{
break;
}
}
}
if (!containerSupported)
{
list.Add(TranscodeReason.ContainerNotSupported);
}
if (videoStream != null && !videoSupported)
{
list.Add(TranscodeReason.VideoCodecNotSupported);
}
if (audioStream != null && !audioSupported)
{
list.Add(TranscodeReason.AudioCodecNotSupported);
}
return list;
}
private int? GetDefaultSubtitleStreamIndex(MediaSourceInfo item, SubtitleProfile[] subtitleProfiles)
{
int highestScore = -1;
foreach (MediaStream stream in item.MediaStreams)
{
if (stream.Type == MediaStreamType.Subtitle && stream.Score.HasValue)
{
if (stream.Score.Value > highestScore)
{
highestScore = stream.Score.Value;
}
}
}
var topStreams = new List<MediaStream>();
foreach (MediaStream stream in item.MediaStreams)
{
if (stream.Type == MediaStreamType.Subtitle && stream.Score.HasValue && stream.Score.Value == highestScore)
{
topStreams.Add(stream);
}
}
// If multiple streams have an equal score, try to pick the most efficient one
if (topStreams.Count > 1)
{
foreach (MediaStream stream in topStreams)
{
foreach (SubtitleProfile profile in subtitleProfiles)
{
if (profile.Method == SubtitleDeliveryMethod.External && StringHelper.EqualsIgnoreCase(profile.Format, stream.Codec))
{
return stream.Index;
}
}
}
}
// If no optimization panned out, just use the original default
return item.DefaultSubtitleStreamIndex;
}
private void SetStreamInfoOptionsFromTranscodingProfile(StreamInfo playlistItem, TranscodingProfile transcodingProfile)
{
if (string.IsNullOrEmpty(transcodingProfile.AudioCodec))
{
playlistItem.AudioCodecs = Array.Empty<string>();
}
else
{
playlistItem.AudioCodecs = transcodingProfile.AudioCodec.Split(',');
}
playlistItem.Container = transcodingProfile.Container;
playlistItem.EstimateContentLength = transcodingProfile.EstimateContentLength;
playlistItem.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo;
if (string.IsNullOrEmpty(transcodingProfile.VideoCodec))
{
playlistItem.VideoCodecs = Array.Empty<string>();
}
else
{
playlistItem.VideoCodecs = transcodingProfile.VideoCodec.Split(',');
}
playlistItem.CopyTimestamps = transcodingProfile.CopyTimestamps;
playlistItem.EnableSubtitlesInManifest = transcodingProfile.EnableSubtitlesInManifest;
playlistItem.EnableMpegtsM2TsMode = transcodingProfile.EnableMpegtsM2TsMode;
playlistItem.BreakOnNonKeyFrames = transcodingProfile.BreakOnNonKeyFrames;
if (transcodingProfile.MinSegments > 0)
{
playlistItem.MinSegments = transcodingProfile.MinSegments;
}
if (transcodingProfile.SegmentLength > 0)
{
playlistItem.SegmentLength = transcodingProfile.SegmentLength;
}
playlistItem.SubProtocol = transcodingProfile.Protocol;
if (!string.IsNullOrEmpty(transcodingProfile.MaxAudioChannels))
{
int transcodingMaxAudioChannels;
if (int.TryParse(transcodingProfile.MaxAudioChannels, NumberStyles.Any, CultureInfo.InvariantCulture, out transcodingMaxAudioChannels))
{
playlistItem.TranscodingMaxAudioChannels = transcodingMaxAudioChannels;
}
}
}
private StreamInfo BuildVideoItem(MediaSourceInfo item, VideoOptions options)
{
if (item == null)
{
throw new ArgumentNullException(nameof(item));
}
var transcodeReasons = new List<TranscodeReason>();
StreamInfo playlistItem = new StreamInfo
{
ItemId = options.ItemId,
MediaType = DlnaProfileType.Video,
MediaSource = item,
RunTimeTicks = item.RunTimeTicks,
Context = options.Context,
DeviceProfile = options.Profile
};
playlistItem.SubtitleStreamIndex = options.SubtitleStreamIndex ?? GetDefaultSubtitleStreamIndex(item, options.Profile.SubtitleProfiles);
MediaStream subtitleStream = playlistItem.SubtitleStreamIndex.HasValue ? item.GetMediaStream(MediaStreamType.Subtitle, playlistItem.SubtitleStreamIndex.Value) : null;
MediaStream audioStream = item.GetDefaultAudioStream(options.AudioStreamIndex ?? item.DefaultAudioStreamIndex);
if (audioStream != null)
{
playlistItem.AudioStreamIndex = audioStream.Index;
}
MediaStream videoStream = item.VideoStream;
// TODO: This doesn't accout for situation of device being able to handle media bitrate, but wifi connection not fast enough
var directPlayEligibilityResult = IsEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options, true) ?? 0, subtitleStream, options, PlayMethod.DirectPlay);
var directStreamEligibilityResult = IsEligibleForDirectPlay(item, options.GetMaxBitrate(false) ?? 0, subtitleStream, options, PlayMethod.DirectStream);
bool isEligibleForDirectPlay = options.EnableDirectPlay && (options.ForceDirectPlay || directPlayEligibilityResult.Item1);
bool isEligibleForDirectStream = options.EnableDirectStream && (options.ForceDirectStream || directStreamEligibilityResult.Item1);
_logger.LogInformation("Profile: {0}, Path: {1}, isEligibleForDirectPlay: {2}, isEligibleForDirectStream: {3}",
options.Profile.Name ?? "Unknown Profile",
item.Path ?? "Unknown path",
isEligibleForDirectPlay,
isEligibleForDirectStream);
if (isEligibleForDirectPlay || isEligibleForDirectStream)
{
// See if it can be direct played
var directPlayInfo = GetVideoDirectPlayProfile(options, item, videoStream, audioStream, isEligibleForDirectPlay, isEligibleForDirectStream);
var directPlay = directPlayInfo.Item1;
if (directPlay != null)
{
playlistItem.PlayMethod = directPlay.Value;
playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, item.Path, options.Profile, DlnaProfileType.Video);
if (subtitleStream != null)
{
SubtitleProfile subtitleProfile = GetSubtitleProfile(item, subtitleStream, options.Profile.SubtitleProfiles, directPlay.Value, _transcoderSupport, item.Container, null);
playlistItem.SubtitleDeliveryMethod = subtitleProfile.Method;
playlistItem.SubtitleFormat = subtitleProfile.Format;
}
return playlistItem;
}
transcodeReasons.AddRange(directPlayInfo.Item2);
}
if (directPlayEligibilityResult.Item2.HasValue)
{
transcodeReasons.Add(directPlayEligibilityResult.Item2.Value);
}
if (directStreamEligibilityResult.Item2.HasValue)
{
transcodeReasons.Add(directStreamEligibilityResult.Item2.Value);
}
// Can't direct play, find the transcoding profile
TranscodingProfile transcodingProfile = null;
foreach (TranscodingProfile i in options.Profile.TranscodingProfiles)
{
if (i.Type == playlistItem.MediaType && i.Context == options.Context)
{
transcodingProfile = i;
break;
}
}
if (transcodingProfile != null)
{
if (!item.SupportsTranscoding)
{
return null;
}
if (subtitleStream != null)
{
SubtitleProfile 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.PlayMethod = PlayMethod.Transcode;
SetStreamInfoOptionsFromTranscodingProfile(playlistItem, transcodingProfile);
ConditionProcessor conditionProcessor = new ConditionProcessor();
var isFirstAppliedCodecProfile = true;
foreach (CodecProfile i in options.Profile.CodecProfiles)
{
if (i.Type == CodecType.Video && i.ContainsAnyCodec(transcodingProfile.VideoCodec, transcodingProfile.Container))
{
bool applyConditions = true;
foreach (ProfileCondition applyCondition in i.ApplyConditions)
{
int? width = videoStream == null ? null : videoStream.Width;
int? height = videoStream == null ? null : videoStream.Height;
int? bitDepth = videoStream == null ? null : videoStream.BitDepth;
int? videoBitrate = videoStream == null ? null : videoStream.BitRate;
double? videoLevel = videoStream == null ? null : videoStream.Level;
string videoProfile = videoStream == null ? null : videoStream.Profile;
float videoFramerate = videoStream == null ? 0 : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate ?? 0;
bool? isAnamorphic = videoStream == null ? null : videoStream.IsAnamorphic;
bool? isInterlaced = videoStream == null ? (bool?)null : videoStream.IsInterlaced;
string videoCodecTag = videoStream == null ? null : videoStream.CodecTag;
bool? isAvc = videoStream == null ? null : videoStream.IsAVC;
TransportStreamTimestamp? timestamp = videoStream == null ? TransportStreamTimestamp.None : item.Timestamp;
int? packetLength = videoStream == null ? null : videoStream.PacketLength;
int? refFrames = videoStream == null ? null : videoStream.RefFrames;
int? numAudioStreams = item.GetStreamCount(MediaStreamType.Audio);
int? numVideoStreams = item.GetStreamCount(MediaStreamType.Video);
if (!conditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc))
{
//LogConditionFailure(options.Profile, "VideoCodecProfile.ApplyConditions", applyCondition, item);
applyConditions = false;
break;
}
}
if (applyConditions)
{
var transcodingVideoCodecs = ContainerProfile.SplitValue(transcodingProfile.VideoCodec);
foreach (var transcodingVideoCodec in transcodingVideoCodecs)
{
if (i.ContainsAnyCodec(transcodingVideoCodec, transcodingProfile.Container))
{
ApplyTranscodingConditions(playlistItem, i.Conditions, transcodingVideoCodec, true, isFirstAppliedCodecProfile);
isFirstAppliedCodecProfile = false;
}
}
}
}
}
// Honor requested max channels
playlistItem.GlobalMaxAudioChannels = options.MaxAudioChannels;
int audioBitrate = GetAudioBitrate(playlistItem.SubProtocol, options.GetMaxBitrate(false) ?? 0, playlistItem.TargetAudioCodec, audioStream, playlistItem);
playlistItem.AudioBitrate = Math.Min(playlistItem.AudioBitrate ?? audioBitrate, audioBitrate);
isFirstAppliedCodecProfile = true;
foreach (CodecProfile i in options.Profile.CodecProfiles)
{
if (i.Type == CodecType.VideoAudio && i.ContainsAnyCodec(transcodingProfile.AudioCodec, transcodingProfile.Container))
{
bool applyConditions = true;
foreach (ProfileCondition applyCondition in i.ApplyConditions)
{
bool? isSecondaryAudio = audioStream == null ? null : item.IsSecondaryAudio(audioStream);
int? inputAudioBitrate = audioStream == null ? null : audioStream.BitRate;
int? audioChannels = audioStream == null ? null : audioStream.Channels;
string audioProfile = audioStream == null ? null : audioStream.Profile;
int? inputAudioSampleRate = audioStream == null ? null : audioStream.SampleRate;
int? inputAudioBitDepth = audioStream == null ? null : audioStream.BitDepth;
if (!conditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth, audioProfile, isSecondaryAudio))
{
//LogConditionFailure(options.Profile, "VideoCodecProfile.ApplyConditions", applyCondition, item);
applyConditions = false;
break;
}
}
if (applyConditions)
{
var transcodingAudioCodecs = ContainerProfile.SplitValue(transcodingProfile.AudioCodec);
foreach (var transcodingAudioCodec in transcodingAudioCodecs)
{
if (i.ContainsAnyCodec(transcodingAudioCodec, transcodingProfile.Container))
{
ApplyTranscodingConditions(playlistItem, i.Conditions, transcodingAudioCodec, true, isFirstAppliedCodecProfile);
isFirstAppliedCodecProfile = false;
}
}
}
}
}
var maxBitrateSetting = options.GetMaxBitrate(false);
// Honor max rate
if (maxBitrateSetting.HasValue)
{
var availableBitrateForVideo = maxBitrateSetting.Value;
if (playlistItem.AudioBitrate.HasValue)
{
availableBitrateForVideo -= playlistItem.AudioBitrate.Value;
}
// Make sure the video bitrate is lower than bitrate settings but at least 64k
long currentValue = playlistItem.VideoBitrate ?? availableBitrateForVideo;
var longBitrate = Math.Max(Math.Min(availableBitrateForVideo, currentValue), 64000);
playlistItem.VideoBitrate = longBitrate >= int.MaxValue ? int.MaxValue : Convert.ToInt32(longBitrate);
}
}
playlistItem.TranscodeReasons = transcodeReasons.ToArray();
return playlistItem;
}
private int GetDefaultAudioBitrateIfUnknown(MediaStream audioStream)
{
if ((audioStream.Channels ?? 0) >= 6)
{
return 384000;
}
return 192000;
}
private int GetAudioBitrate(string subProtocol, long maxTotalBitrate, string[] targetAudioCodecs, MediaStream audioStream, StreamInfo item)
{
var targetAudioCodec = targetAudioCodecs.Length == 0 ? null : targetAudioCodecs[0];
var targetAudioChannels = item.GetTargetAudioChannels(targetAudioCodec);
int defaultBitrate = audioStream == null ? 192000 : audioStream.BitRate ?? GetDefaultAudioBitrateIfUnknown(audioStream);
// Reduce the bitrate if we're downmixing
if (targetAudioChannels.HasValue && audioStream != null && audioStream.Channels.HasValue && targetAudioChannels.Value < audioStream.Channels.Value)
{
defaultBitrate = targetAudioChannels.Value <= 2 ? 128000 : 192000;
}
int encoderAudioBitrateLimit = int.MaxValue;
if (audioStream != null)
{
// Seeing webm encoding failures when source has 1 audio channel and 22k bitrate.
// Any attempts to transcode over 64k will fail
if (audioStream.Channels.HasValue &&
audioStream.Channels.Value == 1)
{
if ((audioStream.BitRate ?? 0) < 64000)
{
encoderAudioBitrateLimit = 64000;
}
}
}
if (maxTotalBitrate > 0)
{
defaultBitrate = Math.Min(GetMaxAudioBitrateForTotalBitrate(maxTotalBitrate), defaultBitrate);
}
return Math.Min(defaultBitrate, encoderAudioBitrateLimit);
}
private int GetMaxAudioBitrateForTotalBitrate(long totalBitrate)
{
if (totalBitrate <= 640000)
{
return 128000;
}
if (totalBitrate <= 2000000)
{
return 384000;
}
if (totalBitrate <= 3000000)
{
return 448000;
}
return 640000;
}
private Tuple<PlayMethod?, List<TranscodeReason>> GetVideoDirectPlayProfile(VideoOptions options,
MediaSourceInfo mediaSource,
MediaStream videoStream,
MediaStream audioStream,
bool isEligibleForDirectPlay,
bool isEligibleForDirectStream)
{
DeviceProfile profile = options.Profile;
if (options.ForceDirectPlay)
{
return new Tuple<PlayMethod?, List<TranscodeReason>>(PlayMethod.DirectPlay, new List<TranscodeReason>());
}
if (options.ForceDirectStream)
{
return new Tuple<PlayMethod?, List<TranscodeReason>>(PlayMethod.DirectStream, new List<TranscodeReason>());
}
// See if it can be direct played
DirectPlayProfile directPlay = null;
foreach (DirectPlayProfile i in profile.DirectPlayProfiles)
{
if (i.Type == DlnaProfileType.Video && IsVideoDirectPlaySupported(i, mediaSource, videoStream, audioStream))
{
directPlay = i;
break;
}
}
if (directPlay == null)
{
_logger.LogInformation("Profile: {0}, No direct play profiles found for Path: {1}",
profile.Name ?? "Unknown Profile",
mediaSource.Path ?? "Unknown path");
return new Tuple<PlayMethod?, List<TranscodeReason>>(null, GetTranscodeReasonsFromDirectPlayProfile(mediaSource, videoStream, audioStream, profile.DirectPlayProfiles));
}
string container = mediaSource.Container;
var conditions = new List<ProfileCondition>();
foreach (ContainerProfile i in profile.ContainerProfiles)
{
if (i.Type == DlnaProfileType.Video &&
i.ContainsContainer(container))
{
foreach (ProfileCondition c in i.Conditions)
{
conditions.Add(c);
}
}
}
ConditionProcessor conditionProcessor = new ConditionProcessor();
int? width = videoStream == null ? null : videoStream.Width;
int? height = videoStream == null ? null : videoStream.Height;
int? bitDepth = videoStream == null ? null : videoStream.BitDepth;
int? videoBitrate = videoStream == null ? null : videoStream.BitRate;
double? videoLevel = videoStream == null ? null : videoStream.Level;
string videoProfile = videoStream == null ? null : videoStream.Profile;
float videoFramerate = videoStream == null ? 0 : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate ?? 0;
bool? isAnamorphic = videoStream == null ? null : videoStream.IsAnamorphic;
bool? isInterlaced = videoStream == null ? (bool?)null : videoStream.IsInterlaced;
string videoCodecTag = videoStream == null ? null : videoStream.CodecTag;
bool? isAvc = videoStream == null ? null : videoStream.IsAVC;
int? audioBitrate = audioStream == null ? null : audioStream.BitRate;
int? audioChannels = audioStream == null ? null : audioStream.Channels;
string audioProfile = audioStream == null ? null : audioStream.Profile;
int? audioSampleRate = audioStream == null ? null : audioStream.SampleRate;
int? audioBitDepth = audioStream == null ? null : audioStream.BitDepth;
TransportStreamTimestamp? timestamp = videoStream == null ? TransportStreamTimestamp.None : mediaSource.Timestamp;
int? packetLength = videoStream == null ? null : videoStream.PacketLength;
int? refFrames = videoStream == null ? null : videoStream.RefFrames;
int? numAudioStreams = mediaSource.GetStreamCount(MediaStreamType.Audio);
int? numVideoStreams = mediaSource.GetStreamCount(MediaStreamType.Video);
// Check container conditions
foreach (ProfileCondition i in conditions)
{
if (!conditionProcessor.IsVideoConditionSatisfied(i, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc))
{
LogConditionFailure(profile, "VideoContainerProfile", i, mediaSource);
var transcodeReason = GetTranscodeReasonForFailedCondition(i);
var transcodeReasons = transcodeReason.HasValue
? new List<TranscodeReason> { transcodeReason.Value }
: new List<TranscodeReason> { };
return new Tuple<PlayMethod?, List<TranscodeReason>>(null, transcodeReasons);
}
}
string videoCodec = videoStream == null ? null : videoStream.Codec;
conditions = new List<ProfileCondition>();
foreach (CodecProfile i in profile.CodecProfiles)
{
if (i.Type == CodecType.Video && i.ContainsAnyCodec(videoCodec, container))
{
bool applyConditions = true;
foreach (ProfileCondition applyCondition in i.ApplyConditions)
{
if (!conditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc))
{
//LogConditionFailure(profile, "VideoCodecProfile.ApplyConditions", applyCondition, mediaSource);
applyConditions = false;
break;
}
}
if (applyConditions)
{
foreach (ProfileCondition c in i.Conditions)
{
conditions.Add(c);
}
}
}
}
foreach (ProfileCondition i in conditions)
{
if (!conditionProcessor.IsVideoConditionSatisfied(i, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc))
{
LogConditionFailure(profile, "VideoCodecProfile", i, mediaSource);
var transcodeReason = GetTranscodeReasonForFailedCondition(i);
var transcodeReasons = transcodeReason.HasValue
? new List<TranscodeReason> { transcodeReason.Value }
: new List<TranscodeReason> { };
return new Tuple<PlayMethod?, List<TranscodeReason>>(null, transcodeReasons);
}
}
if (audioStream != null)
{
string audioCodec = audioStream.Codec;
conditions = new List<ProfileCondition>();
bool? isSecondaryAudio = audioStream == null ? null : mediaSource.IsSecondaryAudio(audioStream);
foreach (CodecProfile i in profile.CodecProfiles)
{
if (i.Type == CodecType.VideoAudio && i.ContainsAnyCodec(audioCodec, container))
{
bool applyConditions = true;
foreach (ProfileCondition applyCondition in i.ApplyConditions)
{
if (!conditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, audioBitrate, audioSampleRate, audioBitDepth, audioProfile, isSecondaryAudio))
{
//LogConditionFailure(profile, "VideoAudioCodecProfile.ApplyConditions", applyCondition, mediaSource);
applyConditions = false;
break;
}
}
if (applyConditions)
{
foreach (ProfileCondition c in i.Conditions)
{
conditions.Add(c);
}
}
}
}
foreach (ProfileCondition i in conditions)
{
if (!conditionProcessor.IsVideoAudioConditionSatisfied(i, audioChannels, audioBitrate, audioSampleRate, audioBitDepth, audioProfile, isSecondaryAudio))
{
LogConditionFailure(profile, "VideoAudioCodecProfile", i, mediaSource);
var transcodeReason = GetTranscodeReasonForFailedCondition(i);
var transcodeReasons = transcodeReason.HasValue
? new List<TranscodeReason> { transcodeReason.Value }
: new List<TranscodeReason> { };
return new Tuple<PlayMethod?, List<TranscodeReason>>(null, transcodeReasons);
}
}
}
if (isEligibleForDirectStream && mediaSource.SupportsDirectStream)
{
return new Tuple<PlayMethod?, List<TranscodeReason>>(PlayMethod.DirectStream, new List<TranscodeReason>());
}
return new Tuple<PlayMethod?, List<TranscodeReason>>(null, new List<TranscodeReason> { TranscodeReason.ContainerBitrateExceedsLimit });
}
private void LogConditionFailure(DeviceProfile profile, string type, ProfileCondition condition, MediaSourceInfo mediaSource)
{
_logger.LogInformation("Profile: {0}, DirectPlay=false. Reason={1}.{2} Condition: {3}. ConditionValue: {4}. IsRequired: {5}. Path: {6}",
type,
profile.Name ?? "Unknown Profile",
condition.Property,
condition.Condition,
condition.Value ?? string.Empty,
condition.IsRequired,
mediaSource.Path ?? "Unknown path");
}
private ValueTuple<bool, TranscodeReason?> IsEligibleForDirectPlay(MediaSourceInfo item,
long maxBitrate,
MediaStream subtitleStream,
VideoOptions options,
PlayMethod playMethod)
{
if (subtitleStream != null)
{
SubtitleProfile subtitleProfile = GetSubtitleProfile(item, subtitleStream, options.Profile.SubtitleProfiles, playMethod, _transcoderSupport, item.Container, null);
if (subtitleProfile.Method != SubtitleDeliveryMethod.External && subtitleProfile.Method != SubtitleDeliveryMethod.Embed)
{
_logger.LogInformation("Not eligible for {0} due to unsupported subtitles", playMethod);
return new ValueTuple<bool, TranscodeReason?>(false, TranscodeReason.SubtitleCodecNotSupported);
}
}
var result = IsAudioEligibleForDirectPlay(item, maxBitrate, playMethod);
if (result)
{
return new ValueTuple<bool, TranscodeReason?>(result, null);
}
return new ValueTuple<bool, TranscodeReason?>(result, TranscodeReason.ContainerBitrateExceedsLimit);
}
public static SubtitleProfile GetSubtitleProfile(MediaSourceInfo mediaSource, MediaStream subtitleStream, SubtitleProfile[] subtitleProfiles, PlayMethod playMethod, ITranscoderSupport transcoderSupport, string outputContainer, string transcodingSubProtocol)
{
if (!subtitleStream.IsExternal && (playMethod != PlayMethod.Transcode || !string.Equals(transcodingSubProtocol, "hls", StringComparison.OrdinalIgnoreCase)))
{
// Look for supported embedded subs of the same format
foreach (SubtitleProfile profile in subtitleProfiles)
{
if (!profile.SupportsLanguage(subtitleStream.Language))
{
continue;
}
if (profile.Method != SubtitleDeliveryMethod.Embed)
{
continue;
}
if (!ContainerProfile.ContainsContainer(profile.Container, outputContainer))
{
continue;
}
if (playMethod == PlayMethod.Transcode && !IsSubtitleEmbedSupported(subtitleStream, profile, transcodingSubProtocol, outputContainer))
{
continue;
}
if (subtitleStream.IsTextSubtitleStream == MediaStream.IsTextFormat(profile.Format) && StringHelper.EqualsIgnoreCase(profile.Format, subtitleStream.Codec))
{
return profile;
}
}
// Look for supported embedded subs of a convertible format
foreach (SubtitleProfile profile in subtitleProfiles)
{
if (!profile.SupportsLanguage(subtitleStream.Language))
{
continue;
}
if (profile.Method != SubtitleDeliveryMethod.Embed)
{
continue;
}
if (!ContainerProfile.ContainsContainer(profile.Container, outputContainer))
{
continue;
}
if (playMethod == PlayMethod.Transcode && !IsSubtitleEmbedSupported(subtitleStream, profile, transcodingSubProtocol, outputContainer))
{
continue;
}
if (subtitleStream.IsTextSubtitleStream && subtitleStream.SupportsSubtitleConversionTo(profile.Format))
{
return profile;
}
}
}
// Look for an external or hls profile that matches the stream type (text/graphical) and doesn't require conversion
return GetExternalSubtitleProfile(mediaSource, subtitleStream, subtitleProfiles, playMethod, transcoderSupport, false) ??
GetExternalSubtitleProfile(mediaSource, subtitleStream, subtitleProfiles, playMethod, transcoderSupport, true) ??
new SubtitleProfile
{
Method = SubtitleDeliveryMethod.Encode,
Format = subtitleStream.Codec
};
}
private static bool IsSubtitleEmbedSupported(MediaStream subtitleStream, SubtitleProfile subtitleProfile, string transcodingSubProtocol, string transcodingContainer)
{
if (!string.IsNullOrEmpty(transcodingContainer))
{
var normalizedContainers = ContainerProfile.SplitValue(transcodingContainer);
if (ContainerProfile.ContainsContainer(normalizedContainers, "ts"))
{
return false;
}
if (ContainerProfile.ContainsContainer(normalizedContainers, "mpegts"))
{
return false;
}
if (ContainerProfile.ContainsContainer(normalizedContainers, "mp4"))
{
return false;
}
if (ContainerProfile.ContainsContainer(normalizedContainers, "mkv") ||
ContainerProfile.ContainsContainer(normalizedContainers, "matroska"))
{
return true;
}
}
return false;
}
private static SubtitleProfile GetExternalSubtitleProfile(MediaSourceInfo mediaSource, MediaStream subtitleStream, SubtitleProfile[] subtitleProfiles, PlayMethod playMethod, ITranscoderSupport transcoderSupport, bool allowConversion)
{
foreach (SubtitleProfile profile in subtitleProfiles)
{
if (profile.Method != SubtitleDeliveryMethod.External && profile.Method != SubtitleDeliveryMethod.Hls)
{
continue;
}
if (profile.Method == SubtitleDeliveryMethod.Hls && playMethod != PlayMethod.Transcode)
{
continue;
}
if (!profile.SupportsLanguage(subtitleStream.Language))
{
continue;
}
if (!subtitleStream.IsExternal && !transcoderSupport.CanExtractSubtitles(subtitleStream.Codec))
{
continue;
}
if ((profile.Method == SubtitleDeliveryMethod.External && subtitleStream.IsTextSubtitleStream == MediaStream.IsTextFormat(profile.Format)) ||
(profile.Method == SubtitleDeliveryMethod.Hls && subtitleStream.IsTextSubtitleStream))
{
bool requiresConversion = !StringHelper.EqualsIgnoreCase(subtitleStream.Codec, profile.Format);
if (!requiresConversion)
{
return profile;
}
if (!allowConversion)
{
continue;
}
// TODO: Build this into subtitleStream.SupportsExternalStream
if (mediaSource.IsInfiniteStream)
{
continue;
}
if (subtitleStream.IsTextSubtitleStream && subtitleStream.SupportsExternalStream && subtitleStream.SupportsSubtitleConversionTo(profile.Format))
{
return profile;
}
}
}
return null;
}
private bool IsAudioEligibleForDirectPlay(MediaSourceInfo item, long maxBitrate, PlayMethod playMethod)
{
// Don't restrict by bitrate if coming from an external domain
if (item.IsRemote)
{
return true;
}
var requestedMaxBitrate = maxBitrate > 0 ? maxBitrate : 1000000;
// If we don't know the bitrate, then force a transcode if requested max bitrate is under 40 mbps
var itemBitrate = item.Bitrate ??
40000000;
if (itemBitrate > requestedMaxBitrate)
{
_logger.LogInformation("Bitrate exceeds " + playMethod + " limit: media bitrate: {0}, max bitrate: {1}", itemBitrate.ToString(CultureInfo.InvariantCulture), requestedMaxBitrate.ToString(CultureInfo.InvariantCulture));
return false;
}
return true;
}
private void ValidateInput(VideoOptions options)
{
ValidateAudioInput(options);
if (options.AudioStreamIndex.HasValue && string.IsNullOrEmpty(options.MediaSourceId))
{
throw new ArgumentException("MediaSourceId is required when a specific audio stream is requested");
}
if (options.SubtitleStreamIndex.HasValue && string.IsNullOrEmpty(options.MediaSourceId))
{
throw new ArgumentException("MediaSourceId is required when a specific subtitle stream is requested");
}
}
private void ValidateAudioInput(AudioOptions options)
{
if (options.ItemId.Equals(Guid.Empty))
{
throw new ArgumentException("ItemId is required");
}
if (string.IsNullOrEmpty(options.DeviceId))
{
throw new ArgumentException("DeviceId is required");
}
if (options.Profile == null)
{
throw new ArgumentException("Profile is required");
}
if (options.MediaSources == null)
{
throw new ArgumentException("MediaSources is required");
}
}
private void ApplyTranscodingConditions(StreamInfo item, List<CodecProfile> codecProfiles)
{
foreach (var profile in codecProfiles)
{
ApplyTranscodingConditions(item, profile);
}
}
private void ApplyTranscodingConditions(StreamInfo item, CodecProfile codecProfile)
{
var codecs = ContainerProfile.SplitValue(codecProfile.Codec);
if (codecs.Length == 0)
{
ApplyTranscodingConditions(item, codecProfile.Conditions, null, true, true);
return;
}
var enableNonQualified = true;
foreach (var codec in codecs)
{
ApplyTranscodingConditions(item, codecProfile.Conditions, codec, true, enableNonQualified);
enableNonQualified = false;
}
}
private void ApplyTranscodingConditions(StreamInfo item, IEnumerable<ProfileCondition> conditions, string qualifier, bool enableQualifiedConditions, bool enableNonQualifiedConditions)
{
foreach (ProfileCondition condition in conditions)
{
string value = condition.Value;
if (string.IsNullOrEmpty(value))
{
continue;
}
// No way to express this
if (condition.Condition == ProfileConditionType.GreaterThanEqual)
{
continue;
}
switch (condition.Property)
{
case ProfileConditionValue.AudioBitrate:
{
if (!enableNonQualifiedConditions)
{
continue;
}
int num;
if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out num))
{
if (condition.Condition == ProfileConditionType.Equals)
{
item.AudioBitrate = num;
}
else if (condition.Condition == ProfileConditionType.LessThanEqual)
{
item.AudioBitrate = Math.Min(num, item.AudioBitrate ?? num);
}
else if (condition.Condition == ProfileConditionType.GreaterThanEqual)
{
item.AudioBitrate = Math.Max(num, item.AudioBitrate ?? num);
}
}
break;
}
case ProfileConditionValue.AudioChannels:
{
if (string.IsNullOrEmpty(qualifier))
{
if (!enableNonQualifiedConditions)
{
continue;
}
}
else
{
if (!enableQualifiedConditions)
{
continue;
}
}
int num;
if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out num))
{
if (condition.Condition == ProfileConditionType.Equals)
{
item.SetOption(qualifier, "audiochannels", num.ToString(CultureInfo.InvariantCulture));
}
else if (condition.Condition == ProfileConditionType.LessThanEqual)
{
item.SetOption(qualifier, "audiochannels", Math.Min(num, item.GetTargetAudioChannels(qualifier) ?? num).ToString(CultureInfo.InvariantCulture));
}
else if (condition.Condition == ProfileConditionType.GreaterThanEqual)
{
item.SetOption(qualifier, "audiochannels", Math.Max(num, item.GetTargetAudioChannels(qualifier) ?? num).ToString(CultureInfo.InvariantCulture));
}
}
break;
}
case ProfileConditionValue.IsAvc:
{
if (!enableNonQualifiedConditions)
{
continue;
}
bool isAvc;
if (bool.TryParse(value, out isAvc))
{
if (isAvc && condition.Condition == ProfileConditionType.Equals)
{
item.RequireAvc = true;
}
else if (!isAvc && condition.Condition == ProfileConditionType.NotEquals)
{
item.RequireAvc = true;
}
}
break;
}
case ProfileConditionValue.IsAnamorphic:
{
if (!enableNonQualifiedConditions)
{
continue;
}
bool isAnamorphic;
if (bool.TryParse(value, out isAnamorphic))
{
if (isAnamorphic && condition.Condition == ProfileConditionType.Equals)
{
item.RequireNonAnamorphic = true;
}
else if (!isAnamorphic && condition.Condition == ProfileConditionType.NotEquals)
{
item.RequireNonAnamorphic = true;
}
}
break;
}
case ProfileConditionValue.IsInterlaced:
{
if (string.IsNullOrEmpty(qualifier))
{
if (!enableNonQualifiedConditions)
{
continue;
}
}
else
{
if (!enableQualifiedConditions)
{
continue;
}
}
bool isInterlaced;
if (bool.TryParse(value, out isInterlaced))
{
if (!isInterlaced && condition.Condition == ProfileConditionType.Equals)
{
item.SetOption(qualifier, "deinterlace", "true");
}
else if (isInterlaced && condition.Condition == ProfileConditionType.NotEquals)
{
item.SetOption(qualifier, "deinterlace", "true");
}
}
break;
}
case ProfileConditionValue.AudioProfile:
case ProfileConditionValue.Has64BitOffsets:
case ProfileConditionValue.PacketLength:
case ProfileConditionValue.NumAudioStreams:
case ProfileConditionValue.NumVideoStreams:
case ProfileConditionValue.IsSecondaryAudio:
case ProfileConditionValue.VideoTimestamp:
{
// Not supported yet
break;
}
case ProfileConditionValue.RefFrames:
{
if (string.IsNullOrEmpty(qualifier))
{
if (!enableNonQualifiedConditions)
{
continue;
}
}
else
{
if (!enableQualifiedConditions)
{
continue;
}
}
int num;
if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out num))
{
if (condition.Condition == ProfileConditionType.Equals)
{
item.SetOption(qualifier, "maxrefframes", num.ToString(CultureInfo.InvariantCulture));
}
else if (condition.Condition == ProfileConditionType.LessThanEqual)
{
item.SetOption(qualifier, "maxrefframes", Math.Min(num, item.GetTargetRefFrames(qualifier) ?? num).ToString(CultureInfo.InvariantCulture));
}
else if (condition.Condition == ProfileConditionType.GreaterThanEqual)
{
item.SetOption(qualifier, "maxrefframes", Math.Max(num, item.GetTargetRefFrames(qualifier) ?? num).ToString(CultureInfo.InvariantCulture));
}
}
break;
}
case ProfileConditionValue.VideoBitDepth:
{
if (string.IsNullOrEmpty(qualifier))
{
if (!enableNonQualifiedConditions)
{
continue;
}
}
else
{
if (!enableQualifiedConditions)
{
continue;
}
}
int num;
if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out num))
{
if (condition.Condition == ProfileConditionType.Equals)
{
item.SetOption(qualifier, "videobitdepth", num.ToString(CultureInfo.InvariantCulture));
}
else if (condition.Condition == ProfileConditionType.LessThanEqual)
{
item.SetOption(qualifier, "videobitdepth", Math.Min(num, item.GetTargetVideoBitDepth(qualifier) ?? num).ToString(CultureInfo.InvariantCulture));
}
else if (condition.Condition == ProfileConditionType.GreaterThanEqual)
{
item.SetOption(qualifier, "videobitdepth", Math.Max(num, item.GetTargetVideoBitDepth(qualifier) ?? num).ToString(CultureInfo.InvariantCulture));
}
}
break;
}
case ProfileConditionValue.VideoProfile:
{
if (string.IsNullOrEmpty(qualifier))
{
continue;
}
if (!string.IsNullOrEmpty(value))
{
// change from split by | to comma
// strip spaces to avoid having to encode
var values = value
.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
if (condition.Condition == ProfileConditionType.Equals || condition.Condition == ProfileConditionType.EqualsAny)
{
item.SetOption(qualifier, "profile", string.Join(",", values));
}
}
break;
}
case ProfileConditionValue.Height:
{
if (!enableNonQualifiedConditions)
{
continue;
}
int num;
if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out num))
{
if (condition.Condition == ProfileConditionType.Equals)
{
item.MaxHeight = num;
}
else if (condition.Condition == ProfileConditionType.LessThanEqual)
{
item.MaxHeight = Math.Min(num, item.MaxHeight ?? num);
}
else if (condition.Condition == ProfileConditionType.GreaterThanEqual)
{
item.MaxHeight = Math.Max(num, item.MaxHeight ?? num);
}
}
break;
}
case ProfileConditionValue.VideoBitrate:
{
if (!enableNonQualifiedConditions)
{
continue;
}
int num;
if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out num))
{
if (condition.Condition == ProfileConditionType.Equals)
{
item.VideoBitrate = num;
}
else if (condition.Condition == ProfileConditionType.LessThanEqual)
{
item.VideoBitrate = Math.Min(num, item.VideoBitrate ?? num);
}
else if (condition.Condition == ProfileConditionType.GreaterThanEqual)
{
item.VideoBitrate = Math.Max(num, item.VideoBitrate ?? num);
}
}
break;
}
case ProfileConditionValue.VideoFramerate:
{
if (!enableNonQualifiedConditions)
{
continue;
}
float num;
if (float.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out num))
{
if (condition.Condition == ProfileConditionType.Equals)
{
item.MaxFramerate = num;
}
else if (condition.Condition == ProfileConditionType.LessThanEqual)
{
item.MaxFramerate = Math.Min(num, item.MaxFramerate ?? num);
}
else if (condition.Condition == ProfileConditionType.GreaterThanEqual)
{
item.MaxFramerate = Math.Max(num, item.MaxFramerate ?? num);
}
}
break;
}
case ProfileConditionValue.VideoLevel:
{
if (string.IsNullOrEmpty(qualifier))
{
continue;
}
int num;
if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out num))
{
if (condition.Condition == ProfileConditionType.Equals)
{
item.SetOption(qualifier, "level", num.ToString(CultureInfo.InvariantCulture));
}
else if (condition.Condition == ProfileConditionType.LessThanEqual)
{
item.SetOption(qualifier, "level", Math.Min(num, item.GetTargetVideoLevel(qualifier) ?? num).ToString(CultureInfo.InvariantCulture));
}
else if (condition.Condition == ProfileConditionType.GreaterThanEqual)
{
item.SetOption(qualifier, "level", Math.Max(num, item.GetTargetVideoLevel(qualifier) ?? num).ToString(CultureInfo.InvariantCulture));
}
}
break;
}
case ProfileConditionValue.Width:
{
if (!enableNonQualifiedConditions)
{
continue;
}
int num;
if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out num))
{
if (condition.Condition == ProfileConditionType.Equals)
{
item.MaxWidth = num;
}
else if (condition.Condition == ProfileConditionType.LessThanEqual)
{
item.MaxWidth = Math.Min(num, item.MaxWidth ?? num);
}
else if (condition.Condition == ProfileConditionType.GreaterThanEqual)
{
item.MaxWidth = Math.Max(num, item.MaxWidth ?? num);
}
}
break;
}
default:
break;
}
}
}
private bool IsAudioDirectPlaySupported(DirectPlayProfile profile, MediaSourceInfo item, MediaStream audioStream)
{
// Check container type
if (!profile.SupportsContainer(item.Container))
{
return false;
}
// Check audio codec
string audioCodec = audioStream == null ? null : audioStream.Codec;
if (!profile.SupportsAudioCodec(audioCodec))
{
return false;
}
return true;
}
private bool IsVideoDirectPlaySupported(DirectPlayProfile profile, MediaSourceInfo item, MediaStream videoStream, MediaStream audioStream)
{
// Check container type
if (!profile.SupportsContainer(item.Container))
{
return false;
}
// Check video codec
string videoCodec = videoStream == null ? null : videoStream.Codec;
if (!profile.SupportsVideoCodec(videoCodec))
{
return false;
}
// Check audio codec
if (audioStream != null)
{
string audioCodec = audioStream == null ? null : audioStream.Codec;
if (!profile.SupportsAudioCodec(audioCodec))
{
return false;
}
}
return true;
}
}
}