diff options
Diffstat (limited to 'MediaBrowser.Model')
| -rw-r--r-- | MediaBrowser.Model/Dlna/CodecProfile.cs | 4 | ||||
| -rw-r--r-- | MediaBrowser.Model/Dlna/ConditionProcessor.cs | 192 | ||||
| -rw-r--r-- | MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs | 120 | ||||
| -rw-r--r-- | MediaBrowser.Model/Dlna/DeviceProfile.cs | 74 | ||||
| -rw-r--r-- | MediaBrowser.Model/Dlna/DirectPlayProfile.cs | 9 | ||||
| -rw-r--r-- | MediaBrowser.Model/Dlna/DlnaMaps.cs | 13 | ||||
| -rw-r--r-- | MediaBrowser.Model/Dlna/MediaFormatProfile.cs | 2 | ||||
| -rw-r--r-- | MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs | 59 | ||||
| -rw-r--r-- | MediaBrowser.Model/Dlna/StreamBuilder.cs | 305 | ||||
| -rw-r--r-- | MediaBrowser.Model/Dlna/StreamInfo.cs | 99 | ||||
| -rw-r--r-- | MediaBrowser.Model/Entities/MediaStream.cs | 19 | ||||
| -rw-r--r-- | MediaBrowser.Model/MediaBrowser.Model.csproj | 1 |
12 files changed, 657 insertions, 240 deletions
diff --git a/MediaBrowser.Model/Dlna/CodecProfile.cs b/MediaBrowser.Model/Dlna/CodecProfile.cs index b15dfc809e..2b04b7fdb7 100644 --- a/MediaBrowser.Model/Dlna/CodecProfile.cs +++ b/MediaBrowser.Model/Dlna/CodecProfile.cs @@ -76,10 +76,12 @@ namespace MediaBrowser.Model.Dlna Width, Height, Has64BitOffsets, + PacketLength, VideoBitDepth, VideoBitrate, VideoFramerate, VideoLevel, - VideoProfile + VideoProfile, + VideoTimestamp } } diff --git a/MediaBrowser.Model/Dlna/ConditionProcessor.cs b/MediaBrowser.Model/Dlna/ConditionProcessor.cs new file mode 100644 index 0000000000..292ca21487 --- /dev/null +++ b/MediaBrowser.Model/Dlna/ConditionProcessor.cs @@ -0,0 +1,192 @@ +using System; +using System.Globalization; + +namespace MediaBrowser.Model.Dlna +{ + public class ConditionProcessor + { + public bool IsVideoConditionSatisfied(ProfileCondition condition, + int? audioBitrate, + int? audioChannels, + int? width, + int? height, + int? bitDepth, + int? videoBitrate, + string videoProfile, + double? videoLevel, + double? videoFramerate, + int? packetLength, + TransportStreamTimestamp timestamp) + { + switch (condition.Property) + { + case ProfileConditionValue.AudioProfile: + // TODO: Implement + return true; + case ProfileConditionValue.Has64BitOffsets: + // TODO: Implement + return true; + case ProfileConditionValue.VideoFramerate: + return IsConditionSatisfied(condition, videoFramerate); + case ProfileConditionValue.VideoLevel: + return IsConditionSatisfied(condition, videoLevel); + case ProfileConditionValue.VideoProfile: + return IsConditionSatisfied(condition, videoProfile); + case ProfileConditionValue.PacketLength: + return IsConditionSatisfied(condition, packetLength); + case ProfileConditionValue.AudioBitrate: + return IsConditionSatisfied(condition, audioBitrate); + case ProfileConditionValue.AudioChannels: + return IsConditionSatisfied(condition, audioChannels); + case ProfileConditionValue.VideoBitDepth: + return IsConditionSatisfied(condition, bitDepth); + case ProfileConditionValue.VideoBitrate: + return IsConditionSatisfied(condition, videoBitrate); + case ProfileConditionValue.Height: + return IsConditionSatisfied(condition, height); + case ProfileConditionValue.Width: + return IsConditionSatisfied(condition, width); + case ProfileConditionValue.VideoTimestamp: + return IsConditionSatisfied(condition, timestamp); + default: + throw new ArgumentException("Unexpected condition on video file: " + condition.Property); + } + } + + public bool IsImageConditionSatisfied(ProfileCondition condition, int? width, int? height) + { + switch (condition.Property) + { + case ProfileConditionValue.Height: + return IsConditionSatisfied(condition, height); + case ProfileConditionValue.Width: + return IsConditionSatisfied(condition, width); + default: + throw new ArgumentException("Unexpected condition on image file: " + condition.Property); + } + } + + public bool IsAudioConditionSatisfied(ProfileCondition condition, int? audioChannels, int? audioBitrate) + { + switch (condition.Property) + { + case ProfileConditionValue.AudioBitrate: + return IsConditionSatisfied(condition, audioBitrate); + case ProfileConditionValue.AudioChannels: + return IsConditionSatisfied(condition, audioChannels); + default: + throw new ArgumentException("Unexpected condition on audio file: " + condition.Property); + } + } + + public bool IsVideoAudioConditionSatisfied(ProfileCondition condition, + int? audioChannels, + int? audioBitrate) + { + switch (condition.Property) + { + case ProfileConditionValue.AudioBitrate: + return IsConditionSatisfied(condition, audioBitrate); + case ProfileConditionValue.AudioChannels: + return IsConditionSatisfied(condition, audioChannels); + default: + throw new ArgumentException("Unexpected condition on audio file: " + condition.Property); + } + } + + private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); + private bool IsConditionSatisfied(ProfileCondition condition, int? currentValue) + { + if (!currentValue.HasValue) + { + // If the value is unknown, it satisfies if not marked as required + return !condition.IsRequired; + } + + int expected; + if (int.TryParse(condition.Value, NumberStyles.Any, UsCulture, out expected)) + { + switch (condition.Condition) + { + case ProfileConditionType.Equals: + return currentValue.Value.Equals(expected); + case ProfileConditionType.GreaterThanEqual: + return currentValue.Value >= expected; + case ProfileConditionType.LessThanEqual: + return currentValue.Value <= expected; + case ProfileConditionType.NotEquals: + return !currentValue.Value.Equals(expected); + default: + throw new InvalidOperationException("Unexpected ProfileConditionType"); + } + } + + return false; + } + + private bool IsConditionSatisfied(ProfileCondition condition, string currentValue) + { + if (string.IsNullOrEmpty(currentValue)) + { + // If the value is unknown, it satisfies if not marked as required + return !condition.IsRequired; + } + + var expected = condition.Value; + + switch (condition.Condition) + { + case ProfileConditionType.Equals: + return string.Equals(currentValue, expected, StringComparison.OrdinalIgnoreCase); + case ProfileConditionType.NotEquals: + return !string.Equals(currentValue, expected, StringComparison.OrdinalIgnoreCase); + default: + throw new InvalidOperationException("Unexpected ProfileConditionType"); + } + } + + private bool IsConditionSatisfied(ProfileCondition condition, double? currentValue) + { + if (!currentValue.HasValue) + { + // If the value is unknown, it satisfies if not marked as required + return !condition.IsRequired; + } + + double expected; + if (double.TryParse(condition.Value, NumberStyles.Any, UsCulture, out expected)) + { + switch (condition.Condition) + { + case ProfileConditionType.Equals: + return currentValue.Value.Equals(expected); + case ProfileConditionType.GreaterThanEqual: + return currentValue.Value >= expected; + case ProfileConditionType.LessThanEqual: + return currentValue.Value <= expected; + case ProfileConditionType.NotEquals: + return !currentValue.Value.Equals(expected); + default: + throw new InvalidOperationException("Unexpected ProfileConditionType"); + } + } + + return false; + } + + private bool IsConditionSatisfied(ProfileCondition condition, TransportStreamTimestamp timestamp) + { + var expected = (TransportStreamTimestamp)Enum.Parse(typeof(TransportStreamTimestamp), condition.Value, true); + + switch (condition.Condition) + { + case ProfileConditionType.Equals: + return timestamp == expected; + case ProfileConditionType.NotEquals: + return timestamp != expected; + default: + throw new InvalidOperationException("Unexpected ProfileConditionType"); + } + } + } +} diff --git a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs index 351eca5be0..b2bbe3b14c 100644 --- a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs +++ b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; namespace MediaBrowser.Model.Dlna { @@ -11,13 +13,45 @@ namespace MediaBrowser.Model.Dlna _profile = profile; } - public string BuildAudioHeader(string container, + public string BuildImageHeader(string container, + int? width, + int? height) + { + var orgOp = ";DLNA.ORG_OP=" + DlnaMaps.GetImageOrgOpValue(); + + // 0 = native, 1 = transcoded + const string orgCi = ";DLNA.ORG_CI=0"; + + var flagValue = DlnaFlags.StreamingTransferMode | + DlnaFlags.BackgroundTransferMode | + DlnaFlags.DlnaV15; + + var dlnaflags = string.Format(";DLNA.ORG_FLAGS={0}", + FlagsToString(flagValue)); + + var mediaProfile = _profile.GetImageMediaProfile(container, + width, + height); + + var orgPn = mediaProfile == null ? null : mediaProfile.OrgPn; + + if (string.IsNullOrEmpty(orgPn)) + { + orgPn = GetImageOrgPnValue(container, width, height); + } + + var contentFeatures = string.IsNullOrEmpty(orgPn) ? string.Empty : "DLNA.ORG_PN=" + orgPn; + + return (contentFeatures + orgOp + orgCi + dlnaflags).Trim(';'); + } + + public string BuildAudioHeader(string container, string audioCodec, - int? audioBitrate, - int? audioSampleRate, - int? audioChannels, - bool isDirectStream, - long? runtimeTicks, + int? audioBitrate, + int? audioSampleRate, + int? audioChannels, + bool isDirectStream, + long? runtimeTicks, TranscodeSeekInfo transcodeSeekInfo) { // first bit means Time based seek supported, second byte range seek supported (not sure about the order now), so 01 = only byte seek, 10 = time based, 11 = both, 00 = none @@ -42,7 +76,10 @@ namespace MediaBrowser.Model.Dlna var dlnaflags = string.Format(";DLNA.ORG_FLAGS={0}", FlagsToString(flagValue)); - var mediaProfile = _profile.GetAudioMediaProfile(container, audioCodec); + var mediaProfile = _profile.GetAudioMediaProfile(container, + audioCodec, + audioChannels, + audioBitrate); var orgPn = mediaProfile == null ? null : mediaProfile.OrgPn; @@ -62,15 +99,22 @@ namespace MediaBrowser.Model.Dlna return string.Format("{0:X8}{1:D24}", (ulong)flags, 0); } - public string BuildVideoHeader(string container, - string videoCodec, - string audioCodec, - int? width, - int? height, - int? bitrate, - TransportStreamTimestamp timestamp, - bool isDirectStream, - long? runtimeTicks, + public string BuildVideoHeader(string container, + string videoCodec, + string audioCodec, + int? width, + int? height, + int? bitDepth, + int? videoBitrate, + int? audioChannels, + int? audioBitrate, + TransportStreamTimestamp timestamp, + bool isDirectStream, + long? runtimeTicks, + string videoProfile, + double? videoLevel, + double? videoFramerate, + int? packetLength, TranscodeSeekInfo transcodeSeekInfo) { // first bit means Time based seek supported, second byte range seek supported (not sure about the order now), so 01 = only byte seek, 10 = time based, 11 = both, 00 = none @@ -95,12 +139,30 @@ namespace MediaBrowser.Model.Dlna var dlnaflags = string.Format(";DLNA.ORG_FLAGS={0}000000000000000000000000", Enum.Format(typeof(DlnaFlags), flagValue, "x")); - var mediaProfile = _profile.GetVideoMediaProfile(container, audioCodec, videoCodec); + var mediaProfile = _profile.GetVideoMediaProfile(container, + audioCodec, + videoCodec, + audioBitrate, + audioChannels, + width, + height, + bitDepth, + videoBitrate, + videoProfile, + videoLevel, + videoFramerate, + packetLength, + timestamp); + var orgPn = mediaProfile == null ? null : mediaProfile.OrgPn; - + if (string.IsNullOrEmpty(orgPn)) { - orgPn = GetVideoOrgPnValue(container, videoCodec, audioCodec, width, height, bitrate, timestamp); + orgPn = GetVideoOrgPnValue(container, videoCodec, audioCodec, width, height, timestamp) + .FirstOrDefault(); + + // TODO: Support multiple values and return multiple headers? + orgPn = (orgPn ?? string.Empty).Split(',').FirstOrDefault(); } var contentFeatures = string.IsNullOrEmpty(orgPn) ? string.Empty : "DLNA.ORG_PN=" + orgPn; @@ -108,6 +170,16 @@ namespace MediaBrowser.Model.Dlna return (contentFeatures + orgOp + orgCi + dlnaflags).Trim(';'); } + private string GetImageOrgPnValue(string container, int? width, int? height) + { + var format = new MediaFormatProfileResolver() + .ResolveImageFormat(container, + width, + height); + + return format.HasValue ? format.Value.ToString() : null; + } + private string GetAudioOrgPnValue(string container, int? audioBitrate, int? audioSampleRate, int? audioChannels) { var format = new MediaFormatProfileResolver() @@ -119,18 +191,16 @@ namespace MediaBrowser.Model.Dlna return format.HasValue ? format.Value.ToString() : null; } - private string GetVideoOrgPnValue(string container, string videoCodec, string audioCodec, int? width, int? height, int? bitrate, TransportStreamTimestamp timestamp) + private IEnumerable<string> GetVideoOrgPnValue(string container, string videoCodec, string audioCodec, int? width, int? height, TransportStreamTimestamp timestamp) { - var videoFormat = new MediaFormatProfileResolver() + return new MediaFormatProfileResolver() .ResolveVideoFormat(container, videoCodec, audioCodec, width, height, - bitrate, - timestamp); - - return videoFormat.HasValue ? videoFormat.Value.ToString() : null; + timestamp) + .Select(i => i.ToString()); } } } diff --git a/MediaBrowser.Model/Dlna/DeviceProfile.cs b/MediaBrowser.Model/Dlna/DeviceProfile.cs index 106ba75b08..96ff108962 100644 --- a/MediaBrowser.Model/Dlna/DeviceProfile.cs +++ b/MediaBrowser.Model/Dlna/DeviceProfile.cs @@ -1,5 +1,4 @@ -using MediaBrowser.Model.Entities; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Xml.Serialization; @@ -71,6 +70,8 @@ namespace MediaBrowser.Model.Dlna public bool RequiresPlainVideoItems { get; set; } public bool RequiresPlainFolders { get; set; } + public XmlAttribute[] ContentDirectoryRootAttributes { get; set; } + /// <summary> /// Gets or sets the direct play profiles. /// </summary> @@ -96,6 +97,8 @@ namespace MediaBrowser.Model.Dlna CodecProfiles = new CodecProfile[] { }; ContainerProfiles = new ContainerProfile[] { }; + ContentDirectoryRootAttributes = new XmlAttribute[] { }; + SupportedMediaTypes = "Audio,Photo,Video"; } @@ -159,7 +162,7 @@ namespace MediaBrowser.Model.Dlna }); } - public ResponseProfile GetAudioMediaProfile(string container, string audioCodec) + public ResponseProfile GetAudioMediaProfile(string container, string audioCodec, int? audioChannels, int? audioBitrate) { container = (container ?? string.Empty).TrimStart('.'); @@ -182,11 +185,51 @@ namespace MediaBrowser.Model.Dlna return false; } - return true; + var conditionProcessor = new ConditionProcessor(); + return i.Conditions.All(c => conditionProcessor.IsAudioConditionSatisfied(c, + audioChannels, + audioBitrate)); }); } - public ResponseProfile GetVideoMediaProfile(string container, string audioCodec, string videoCodec) + public ResponseProfile GetImageMediaProfile(string container, int? width, int? height) + { + container = (container ?? string.Empty).TrimStart('.'); + + return ResponseProfiles.FirstOrDefault(i => + { + if (i.Type != DlnaProfileType.Photo) + { + return false; + } + + var containers = i.GetContainers().ToList(); + if (containers.Count > 0 && !containers.Contains(container)) + { + return false; + } + + var conditionProcessor = new ConditionProcessor(); + return i.Conditions.All(c => conditionProcessor.IsImageConditionSatisfied(c, + width, + height)); + }); + } + + public ResponseProfile GetVideoMediaProfile(string container, + string audioCodec, + string videoCodec, + int? audioBitrate, + int? audioChannels, + int? width, + int? height, + int? bitDepth, + int? videoBitrate, + string videoProfile, + double? videoLevel, + double? videoFramerate, + int? packetLength, + TransportStreamTimestamp timestamp) { container = (container ?? string.Empty).TrimStart('.'); @@ -215,11 +258,23 @@ namespace MediaBrowser.Model.Dlna return false; } - return true; + var conditionProcessor = new ConditionProcessor(); + return i.Conditions.All(c => conditionProcessor.IsVideoConditionSatisfied(c, + audioBitrate, + audioChannels, + width, + height, + bitDepth, + videoBitrate, + videoProfile, + videoLevel, + videoFramerate, + packetLength, + timestamp)); }); } - public ResponseProfile GetPhotoMediaProfile(string container) + public ResponseProfile GetPhotoMediaProfile(string container, int? width, int? height) { container = (container ?? string.Empty).TrimStart('.'); @@ -236,7 +291,10 @@ namespace MediaBrowser.Model.Dlna return false; } - return true; + var conditionProcessor = new ConditionProcessor(); + return i.Conditions.All(c => conditionProcessor.IsImageConditionSatisfied(c, + width, + height)); }); } } diff --git a/MediaBrowser.Model/Dlna/DirectPlayProfile.cs b/MediaBrowser.Model/Dlna/DirectPlayProfile.cs index c7ecdc908b..e195c94507 100644 --- a/MediaBrowser.Model/Dlna/DirectPlayProfile.cs +++ b/MediaBrowser.Model/Dlna/DirectPlayProfile.cs @@ -34,6 +34,15 @@ namespace MediaBrowser.Model.Dlna } } + public class XmlAttribute + { + [XmlAttribute("name")] + public string Name { get; set; } + + [XmlAttribute("value")] + public string Value { get; set; } + } + public enum DlnaProfileType { Audio = 0, diff --git a/MediaBrowser.Model/Dlna/DlnaMaps.cs b/MediaBrowser.Model/Dlna/DlnaMaps.cs index eb0c333154..d2871474ad 100644 --- a/MediaBrowser.Model/Dlna/DlnaMaps.cs +++ b/MediaBrowser.Model/Dlna/DlnaMaps.cs @@ -41,6 +41,19 @@ namespace MediaBrowser.Model.Dlna // No seeking is available if we don't know the content runtime return "00"; } + + public static string GetImageOrgOpValue() + { + var orgOp = string.Empty; + + // Time-based seeking currently only possible when transcoding + orgOp += "0"; + + // Byte-based seeking only possible when not transcoding + orgOp += "1"; + + return orgOp; + } } [Flags] diff --git a/MediaBrowser.Model/Dlna/MediaFormatProfile.cs b/MediaBrowser.Model/Dlna/MediaFormatProfile.cs index e686fe932d..596985bdc0 100644 --- a/MediaBrowser.Model/Dlna/MediaFormatProfile.cs +++ b/MediaBrowser.Model/Dlna/MediaFormatProfile.cs @@ -1,6 +1,4 @@ -using System; - namespace MediaBrowser.Model.Dlna { public enum MediaFormatProfile diff --git a/MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs b/MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs index 02400a7ff3..9337a6c95f 100644 --- a/MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs +++ b/MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs @@ -1,57 +1,62 @@ using System; using System.Collections.Generic; -using System.Linq; namespace MediaBrowser.Model.Dlna { public class MediaFormatProfileResolver { - public MediaFormatProfile? ResolveVideoFormat(string container, string videoCodec, string audioCodec, int? width, int? height, int? bitrate, TransportStreamTimestamp timestampType) + public IEnumerable<MediaFormatProfile> ResolveVideoFormat(string container, string videoCodec, string audioCodec, int? width, int? height, TransportStreamTimestamp timestampType) { if (string.Equals(container, "asf", StringComparison.OrdinalIgnoreCase)) - return ResolveVideoASFFormat(videoCodec, audioCodec, width, height); + { + var val = ResolveVideoASFFormat(videoCodec, audioCodec, width, height); + return val.HasValue ? new List<MediaFormatProfile> { val.Value } : new List<MediaFormatProfile>(); + } if (string.Equals(container, "mp4", StringComparison.OrdinalIgnoreCase)) - return ResolveVideoMP4Format(videoCodec, audioCodec, width, height, bitrate); + { + var val = ResolveVideoMP4Format(videoCodec, audioCodec, width, height); + return val.HasValue ? new List<MediaFormatProfile> { val.Value } : new List<MediaFormatProfile>(); + } if (string.Equals(container, "avi", StringComparison.OrdinalIgnoreCase)) - return MediaFormatProfile.AVI; + return new[] { MediaFormatProfile.AVI }; if (string.Equals(container, "mkv", StringComparison.OrdinalIgnoreCase)) - return MediaFormatProfile.MATROSKA; + return new[] { MediaFormatProfile.MATROSKA }; if (string.Equals(container, "mpeg2ps", StringComparison.OrdinalIgnoreCase) || string.Equals(container, "ts", StringComparison.OrdinalIgnoreCase)) - // MediaFormatProfile.MPEG_PS_PAL, MediaFormatProfile.MPEG_PS_NTSC - return MediaFormatProfile.MPEG_PS_NTSC; + + return new[] { MediaFormatProfile.MPEG_PS_NTSC, MediaFormatProfile.MPEG_PS_PAL }; if (string.Equals(container, "mpeg1video", StringComparison.OrdinalIgnoreCase)) - return MediaFormatProfile.MPEG1; + return new[] { MediaFormatProfile.MPEG1 }; if (string.Equals(container, "mpeg2ts", StringComparison.OrdinalIgnoreCase) || string.Equals(container, "mpegts", StringComparison.OrdinalIgnoreCase) || string.Equals(container, "m2ts", StringComparison.OrdinalIgnoreCase)) { - var list = ResolveVideoMPEG2TSFormat(videoCodec, audioCodec, width, height, timestampType) - .ToList(); - - return list.Count > 0 ? list[0] : (MediaFormatProfile?)null; + return ResolveVideoMPEG2TSFormat(videoCodec, audioCodec, width, height, timestampType); } if (string.Equals(container, "flv", StringComparison.OrdinalIgnoreCase)) - return MediaFormatProfile.FLV; + return new[] { MediaFormatProfile.FLV }; if (string.Equals(container, "wtv", StringComparison.OrdinalIgnoreCase)) - return MediaFormatProfile.WTV; + return new[] { MediaFormatProfile.WTV }; if (string.Equals(container, "3gp", StringComparison.OrdinalIgnoreCase)) - return ResolveVideo3GPFormat(videoCodec, audioCodec); + { + var val = ResolveVideo3GPFormat(videoCodec, audioCodec); + return val.HasValue ? new List<MediaFormatProfile> { val.Value } : new List<MediaFormatProfile>(); + } if (string.Equals(container, "ogv", StringComparison.OrdinalIgnoreCase) || string.Equals(container, "ogg", StringComparison.OrdinalIgnoreCase)) - return MediaFormatProfile.OGV; + return new[] { MediaFormatProfile.OGV }; - return null; + return new List<MediaFormatProfile>(); } private IEnumerable<MediaFormatProfile> ResolveVideoMPEG2TSFormat(string videoCodec, string audioCodec, int? width, int? height, TransportStreamTimestamp timestampType) @@ -132,11 +137,16 @@ namespace MediaBrowser.Model.Dlna } return new[] { MediaFormatProfile.VC1_TS_AP_L1_AC3_ISO }; } - // if (audioCodec == AudioCodec.DTS) { - // suffix = suffix.equals("_ISO") ? suffix : "_T"; - // return Collections.singletonList(MediaFormatProfile.valueOf(String.format("VC1_TS_HD_DTS%s", cast(Object[])[ suffix ]))); - // } - //} else if ((videoCodec == VideoCodec.MPEG4) || (videoCodec == VideoCodec.MSMPEG4)) { + if (string.Equals(audioCodec, "dts", StringComparison.OrdinalIgnoreCase)) + { + suffix = string.Equals(suffix, "_ISO") ? suffix : "_T"; + + return new[] { ValueOf(string.Format("VC1_TS_HD_DTS{0}", suffix)) }; + } + + } + else if (string.Equals(videoCodec, "mpeg4", StringComparison.OrdinalIgnoreCase) || string.Equals(videoCodec, "msmpeg4", StringComparison.OrdinalIgnoreCase)) + { // if (audioCodec == AudioCodec.AAC) // return Collections.singletonList(MediaFormatProfile.valueOf(String.format("MPEG4_P2_TS_ASP_AAC%s", cast(Object[])[ suffix ]))); // if (audioCodec == AudioCodec.MP3) @@ -145,7 +155,6 @@ namespace MediaBrowser.Model.Dlna // return Collections.singletonList(MediaFormatProfile.valueOf(String.format("MPEG4_P2_TS_ASP_MPEG2_L2%s", cast(Object[])[ suffix ]))); // if ((audioCodec is null) || (audioCodec == AudioCodec.AC3)) { // return Collections.singletonList(MediaFormatProfile.valueOf(String.format("MPEG4_P2_TS_ASP_AC3%s", cast(Object[])[ suffix ]))); - // } } return new List<MediaFormatProfile>(); @@ -156,7 +165,7 @@ namespace MediaBrowser.Model.Dlna return (MediaFormatProfile)Enum.Parse(typeof(MediaFormatProfile), value, true); } - private MediaFormatProfile? ResolveVideoMP4Format(string videoCodec, string audioCodec, int? width, int? height, int? bitrate) + private MediaFormatProfile? ResolveVideoMP4Format(string videoCodec, string audioCodec, int? width, int? height) { if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase)) { diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 2127af34ae..f3425d2027 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -100,13 +100,23 @@ namespace MediaBrowser.Model.Dlna var audioCodec = audioStream == null ? null : audioStream.Codec; // Make sure audio codec profiles are satisfied - if (!string.IsNullOrEmpty(audioCodec) && options.Profile.CodecProfiles.Where(i => i.Type == CodecType.Audio && i.ContainsCodec(audioCodec)) - .All(i => AreConditionsSatisfied(i.Conditions, item.Path, null, audioStream))) + if (!string.IsNullOrEmpty(audioCodec)) { - playlistItem.IsDirectStream = true; - playlistItem.Container = item.Container; + var conditionProcessor = new ConditionProcessor(); - return playlistItem; + var conditions = options.Profile.CodecProfiles.Where(i => i.Type == CodecType.Audio && i.ContainsCodec(audioCodec)) + .SelectMany(i => i.Conditions); + + var audioChannels = audioStream == null ? null : audioStream.Channels; + var audioBitrate = audioStream == null ? null : audioStream.BitRate; + + if (conditions.All(c => conditionProcessor.IsAudioConditionSatisfied(c, audioChannels, audioBitrate))) + { + playlistItem.IsDirectStream = true; + playlistItem.Container = item.Container; + + return playlistItem; + } } } } @@ -168,29 +178,14 @@ namespace MediaBrowser.Model.Dlna if (IsEligibleForDirectPlay(item, options, maxBitrateSetting)) { // See if it can be direct played - var directPlay = options.Profile.DirectPlayProfiles - .FirstOrDefault(i => i.Type == playlistItem.MediaType && IsVideoDirectPlaySupported(i, item, videoStream, audioStream)); + var directPlay = GetVideoDirectPlayProfile(options.Profile, item, videoStream, audioStream); if (directPlay != null) { - var videoCodec = videoStream == null ? null : videoStream.Codec; - - // Make sure video codec profiles are satisfied - if (!string.IsNullOrEmpty(videoCodec) && options.Profile.CodecProfiles.Where(i => i.Type == CodecType.Video && i.ContainsCodec(videoCodec)) - .All(i => AreConditionsSatisfied(i.Conditions, item.Path, videoStream, audioStream))) - { - var audioCodec = audioStream == null ? null : audioStream.Codec; - - // Make sure audio codec profiles are satisfied - if (string.IsNullOrEmpty(audioCodec) || options.Profile.CodecProfiles.Where(i => i.Type == CodecType.VideoAudio && i.ContainsCodec(audioCodec)) - .All(i => AreConditionsSatisfied(i.Conditions, item.Path, videoStream, audioStream))) - { - playlistItem.IsDirectStream = true; - playlistItem.Container = item.Container; + playlistItem.IsDirectStream = true; + playlistItem.Container = item.Container; - return playlistItem; - } - } + return playlistItem; } } @@ -257,6 +252,110 @@ namespace MediaBrowser.Model.Dlna return playlistItem; } + private DirectPlayProfile GetVideoDirectPlayProfile(DeviceProfile profile, + MediaSourceInfo mediaSource, + MediaStream videoStream, + MediaStream audioStream) + { + // See if it can be direct played + var directPlay = profile.DirectPlayProfiles + .FirstOrDefault(i => i.Type == DlnaProfileType.Video && IsVideoDirectPlaySupported(i, mediaSource, videoStream, audioStream)); + + if (directPlay == null) + { + return null; + } + + var container = mediaSource.Container; + + var conditions = profile.ContainerProfiles + .Where(i => i.Type == DlnaProfileType.Video && i.GetContainers().Contains(container, StringComparer.OrdinalIgnoreCase)) + .SelectMany(i => i.Conditions); + + var conditionProcessor = new ConditionProcessor(); + + var width = videoStream == null ? null : videoStream.Width; + var height = videoStream == null ? null : videoStream.Height; + var bitDepth = videoStream == null ? null : videoStream.BitDepth; + var videoBitrate = videoStream == null ? null : videoStream.BitRate; + var videoLevel = videoStream == null ? null : videoStream.Level; + var videoProfile = videoStream == null ? null : videoStream.Profile; + var videoFramerate = videoStream == null ? null : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate; + + var audioBitrate = audioStream == null ? null : audioStream.BitRate; + var audioChannels = audioStream == null ? null : audioStream.Channels; + + var timestamp = videoStream == null ? TransportStreamTimestamp.NONE : videoStream.Timestamp; + var packetLength = videoStream == null ? null : videoStream.PacketLength; + + // Check container conditions + if (!conditions.All(i => conditionProcessor.IsVideoConditionSatisfied(i, + audioBitrate, + audioChannels, + width, + height, + bitDepth, + videoBitrate, + videoProfile, + videoLevel, + videoFramerate, + packetLength, + timestamp))) + { + return null; + } + + var videoCodec = videoStream == null ? null : videoStream.Codec; + + if (string.IsNullOrEmpty(videoCodec)) + { + return null; + } + + conditions = profile.CodecProfiles + .Where(i => i.Type == CodecType.Video && i.ContainsCodec(videoCodec)) + .SelectMany(i => i.Conditions); + + if (!conditions.All(i => conditionProcessor.IsVideoConditionSatisfied(i, + audioBitrate, + audioChannels, + width, + height, + bitDepth, + videoBitrate, + videoProfile, + videoLevel, + videoFramerate, + packetLength, + timestamp))) + { + return null; + } + + if (audioStream != null) + { + var audioCodec = audioStream.Codec; + + if (string.IsNullOrEmpty(audioCodec)) + { + return null; + } + + conditions = profile.CodecProfiles + .Where(i => i.Type == CodecType.VideoAudio && i.ContainsCodec(audioCodec)) + .SelectMany(i => i.Conditions); + + if (!conditions.All(i => conditionProcessor.IsVideoAudioConditionSatisfied(i, + audioChannels, + audioBitrate))) + { + return null; + } + } + + return directPlay; + } + private bool IsEligibleForDirectPlay(MediaSourceInfo item, VideoOptions options, int? maxBitrate) { if (options.SubtitleStreamIndex.HasValue) @@ -343,12 +442,18 @@ namespace MediaBrowser.Model.Dlna } case ProfileConditionValue.AudioProfile: case ProfileConditionValue.Has64BitOffsets: + case ProfileConditionValue.PacketLength: + case ProfileConditionValue.VideoTimestamp: case ProfileConditionValue.VideoBitDepth: - case ProfileConditionValue.VideoProfile: { // Not supported yet break; } + case ProfileConditionValue.VideoProfile: + { + item.VideoProfile = value; + break; + } case ProfileConditionValue.Height: { int num; @@ -457,155 +562,5 @@ namespace MediaBrowser.Model.Dlna return true; } - - private bool AreConditionsSatisfied(IEnumerable<ProfileCondition> conditions, string mediaPath, MediaStream videoStream, MediaStream audioStream) - { - return conditions.All(i => IsConditionSatisfied(i, mediaPath, videoStream, audioStream)); - } - - /// <summary> - /// Determines whether [is condition satisfied] [the specified condition]. - /// </summary> - /// <param name="condition">The condition.</param> - /// <param name="mediaPath">The media path.</param> - /// <param name="videoStream">The video stream.</param> - /// <param name="audioStream">The audio stream.</param> - /// <returns><c>true</c> if [is condition satisfied] [the specified condition]; otherwise, <c>false</c>.</returns> - /// <exception cref="System.InvalidOperationException">Unexpected ProfileConditionType</exception> - private bool IsConditionSatisfied(ProfileCondition condition, string mediaPath, MediaStream videoStream, MediaStream audioStream) - { - if (condition.Property == ProfileConditionValue.Has64BitOffsets) - { - // TODO: Determine how to evaluate this - } - - if (condition.Property == ProfileConditionValue.VideoProfile) - { - var profile = videoStream == null ? null : videoStream.Profile; - - if (!string.IsNullOrEmpty(profile)) - { - switch (condition.Condition) - { - case ProfileConditionType.Equals: - return string.Equals(profile, condition.Value, StringComparison.OrdinalIgnoreCase); - case ProfileConditionType.NotEquals: - return !string.Equals(profile, condition.Value, StringComparison.OrdinalIgnoreCase); - default: - throw new InvalidOperationException("Unexpected ProfileConditionType"); - } - } - } - - else if (condition.Property == ProfileConditionValue.AudioProfile) - { - var profile = audioStream == null ? null : audioStream.Profile; - - if (!string.IsNullOrEmpty(profile)) - { - switch (condition.Condition) - { - case ProfileConditionType.Equals: - return string.Equals(profile, condition.Value, StringComparison.OrdinalIgnoreCase); - case ProfileConditionType.NotEquals: - return !string.Equals(profile, condition.Value, StringComparison.OrdinalIgnoreCase); - default: - throw new InvalidOperationException("Unexpected ProfileConditionType"); - } - } - } - - else - { - var actualValue = GetConditionValue(condition, mediaPath, videoStream, audioStream); - - if (actualValue.HasValue) - { - double expected; - if (double.TryParse(condition.Value, NumberStyles.Any, _usCulture, out expected)) - { - switch (condition.Condition) - { - case ProfileConditionType.Equals: - return actualValue.Value.Equals(expected); - case ProfileConditionType.GreaterThanEqual: - return actualValue.Value >= expected; - case ProfileConditionType.LessThanEqual: - return actualValue.Value <= expected; - case ProfileConditionType.NotEquals: - return !actualValue.Value.Equals(expected); - default: - throw new InvalidOperationException("Unexpected ProfileConditionType"); - } - } - } - } - - // Value doesn't exist in metadata. Fail it if required. - return !condition.IsRequired; - } - - /// <summary> - /// Gets the condition value. - /// </summary> - /// <param name="condition">The condition.</param> - /// <param name="mediaPath">The media path.</param> - /// <param name="videoStream">The video stream.</param> - /// <param name="audioStream">The audio stream.</param> - /// <returns>System.Nullable{System.Int64}.</returns> - /// <exception cref="System.InvalidOperationException">Unexpected Property</exception> - private double? GetConditionValue(ProfileCondition condition, string mediaPath, MediaStream videoStream, MediaStream audioStream) - { - switch (condition.Property) - { - case ProfileConditionValue.AudioBitrate: - return audioStream == null ? null : audioStream.BitRate; - case ProfileConditionValue.AudioChannels: - return audioStream == null ? null : audioStream.Channels; - case ProfileConditionValue.VideoBitrate: - return videoStream == null ? null : videoStream.BitRate; - case ProfileConditionValue.VideoFramerate: - return videoStream == null ? null : (videoStream.AverageFrameRate ?? videoStream.RealFrameRate); - case ProfileConditionValue.Height: - return videoStream == null ? null : videoStream.Height; - case ProfileConditionValue.Width: - return videoStream == null ? null : videoStream.Width; - case ProfileConditionValue.VideoLevel: - return videoStream == null ? null : videoStream.Level; - case ProfileConditionValue.VideoBitDepth: - return videoStream == null ? null : GetBitDepth(videoStream); - default: - throw new InvalidOperationException("Unexpected Property"); - } - } - - private int? GetBitDepth(MediaStream videoStream) - { - var eightBit = new List<string> - { - "yuv420p", - "yuv411p", - "yuvj420p", - "uyyvyy411", - "nv12", - "nv21", - "rgb444le", - "rgb444be", - "bgr444le", - "bgr444be", - "yuvj411p" - }; - - if (!string.IsNullOrEmpty(videoStream.PixelFormat)) - { - if (eightBit.Contains(videoStream.PixelFormat, StringComparer.OrdinalIgnoreCase)) - { - return 8; - } - } - - return null; - } } - } diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index c5589322d8..0ab6805e3b 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -26,6 +26,7 @@ namespace MediaBrowser.Model.Dlna public long StartPositionTicks { get; set; } public string VideoCodec { get; set; } + public string VideoProfile { get; set; } public string AudioCodec { get; set; } @@ -57,8 +58,6 @@ namespace MediaBrowser.Model.Dlna public MediaSourceInfo MediaSource { get; set; } - public TransportStreamTimestamp TargetTimestamp { get; set; } - public string MediaSourceId { get @@ -178,6 +177,74 @@ namespace MediaBrowser.Model.Dlna } /// <summary> + /// Predicts the audio sample rate that will be in the output stream + /// </summary> + public int? TargetVideoBitDepth + { + get + { + var stream = TargetVideoStream; + return stream == null || !IsDirectStream ? null : stream.BitDepth; + } + } + + /// <summary> + /// Predicts the audio sample rate that will be in the output stream + /// </summary> + public double? TargetFramerate + { + get + { + var stream = TargetVideoStream; + return MaxFramerate.HasValue && !IsDirectStream + ? MaxFramerate + : stream == null ? null : stream.AverageFrameRate ?? stream.RealFrameRate; + } + } + + /// <summary> + /// Predicts the audio sample rate that will be in the output stream + /// </summary> + public double? TargetVideoLevel + { + get + { + var stream = TargetVideoStream; + return VideoLevel.HasValue && !IsDirectStream + ? VideoLevel + : stream == null ? null : stream.Level; + } + } + + /// <summary> + /// Predicts the audio sample rate that will be in the output stream + /// </summary> + public int? TargetPacketLength + { + get + { + var stream = TargetVideoStream; + return !IsDirectStream + ? null + : stream == null ? null : stream.PacketLength; + } + } + + /// <summary> + /// Predicts the audio sample rate that will be in the output stream + /// </summary> + public string TargetVideoProfile + { + get + { + var stream = TargetVideoStream; + return !string.IsNullOrEmpty(VideoProfile) && !IsDirectStream + ? VideoProfile + : stream == null ? null : stream.Profile; + } + } + + /// <summary> /// Predicts the audio bitrate that will be in the output stream /// </summary> public int? TargetAudioBitrate @@ -256,11 +323,35 @@ namespace MediaBrowser.Model.Dlna } } - public int? TotalOutputBitrate + public int? TargetVideoBitrate + { + get + { + var stream = TargetVideoStream; + + return VideoBitrate.HasValue && !IsDirectStream + ? VideoBitrate + : stream == null ? null : stream.BitRate; + } + } + + public TransportStreamTimestamp TargetTimestamp + { + get + { + var stream = TargetVideoStream; + + return !IsDirectStream + ? TransportStreamTimestamp.VALID + : stream == null ? TransportStreamTimestamp.VALID : stream.Timestamp; + } + } + + public int? TargetTotalBitrate { get { - return (TargetAudioBitrate ?? 0) + (VideoBitrate ?? 0); + return (TargetAudioBitrate ?? 0) + (TargetVideoBitrate ?? 0); } } diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs index cdfdd19c84..920112d02e 100644 --- a/MediaBrowser.Model/Entities/MediaStream.cs +++ b/MediaBrowser.Model/Entities/MediaStream.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Diagnostics; +using MediaBrowser.Model.Dlna; namespace MediaBrowser.Model.Entities { @@ -40,6 +41,24 @@ namespace MediaBrowser.Model.Entities public int? BitRate { get; set; } /// <summary> + /// Gets or sets the bit depth. + /// </summary> + /// <value>The bit depth.</value> + public int? BitDepth { get; set; } + + /// <summary> + /// Gets or sets the length of the packet. + /// </summary> + /// <value>The length of the packet.</value> + public int? PacketLength { get; set; } + + /// <summary> + /// Gets or sets the timestamp. + /// </summary> + /// <value>The timestamp.</value> + public TransportStreamTimestamp Timestamp { get; set; } + + /// <summary> /// Gets or sets the channels. /// </summary> /// <value>The channels.</value> diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index 4d0214eb86..1ae0833fb7 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -67,6 +67,7 @@ <Compile Include="Configuration\MetadataOptions.cs" /> <Compile Include="Configuration\ServerConfiguration.cs" /> <Compile Include="Dlna\CodecProfile.cs" /> + <Compile Include="Dlna\ConditionProcessor.cs" /> <Compile Include="Dlna\ContainerProfile.cs" /> <Compile Include="Dlna\ContentFeatureBuilder.cs" /> <Compile Include="Dlna\DeviceIdentification.cs" /> |
