From a748c660cb3b20fba0f141025a41afa4426a935c Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 24 Apr 2014 01:08:10 -0400 Subject: [PATCH] updated dlna profiles --- .../Playback/BaseStreamingService.cs | 28 +- MediaBrowser.Api/Playback/StreamState.cs | 86 ++++- .../MediaEncoding/MediaEncoderHelpers.cs | 30 ++ MediaBrowser.Dlna/Didl/DidlBuilder.cs | 46 ++- MediaBrowser.Dlna/PlayTo/DlnaController.cs | 9 +- .../Profiles/PanasonicVieraProfile.cs | 14 +- .../Profiles/SamsungSmartTvProfile.cs | 9 + .../Profiles/SonyBlurayPlayer2013Profile.cs | 9 + .../Profiles/SonyBlurayPlayerProfile.cs | 9 + .../Profiles/SonyBravia2010Profile.cs | 39 ++- .../Profiles/SonyBravia2011Profile.cs | 44 ++- .../Profiles/SonyBravia2012Profile.cs | 39 ++- .../Profiles/SonyBravia2013Profile.cs | 39 ++- MediaBrowser.Dlna/Profiles/Xml/Default.xml | 1 + MediaBrowser.Dlna/Profiles/Xml/Denon AVR.xml | 1 + .../Profiles/Xml/LG Smart TV.xml | 1 + .../Profiles/Xml/Linksys DMA2100.xml | 1 + .../Profiles/Xml/Panasonic Viera.xml | 3 + .../Profiles/Xml/Samsung Smart TV.xml | 3 + .../Profiles/Xml/Sony Blu-ray Player 2013.xml | 3 + .../Profiles/Xml/Sony Blu-ray Player.xml | 3 + .../Profiles/Xml/Sony Bravia (2010).xml | 12 +- .../Profiles/Xml/Sony Bravia (2011).xml | 12 +- .../Profiles/Xml/Sony Bravia (2012).xml | 12 +- .../Profiles/Xml/Sony Bravia (2013).xml | 12 +- .../Profiles/Xml/Sony PlayStation 3.xml | 1 + MediaBrowser.Dlna/Profiles/Xml/WDTV Live.xml | 1 + MediaBrowser.Dlna/Profiles/Xml/Xbox 360.xml | 1 + MediaBrowser.Dlna/Profiles/Xml/Xbox One.xml | 1 + MediaBrowser.Dlna/Profiles/Xml/foobar2000.xml | 1 + MediaBrowser.Dlna/Server/ControlHandler.cs | 13 +- .../MediaBrowser.Model.Portable.csproj | 3 + .../MediaBrowser.Model.net35.csproj | 3 + MediaBrowser.Model/Dlna/CodecProfile.cs | 4 +- MediaBrowser.Model/Dlna/ConditionProcessor.cs | 192 +++++++++++ .../Dlna/ContentFeatureBuilder.cs | 120 +++++-- MediaBrowser.Model/Dlna/DeviceProfile.cs | 74 ++++- MediaBrowser.Model/Dlna/DirectPlayProfile.cs | 9 + MediaBrowser.Model/Dlna/DlnaMaps.cs | 13 + MediaBrowser.Model/Dlna/MediaFormatProfile.cs | 2 - .../Dlna/MediaFormatProfileResolver.cs | 59 ++-- MediaBrowser.Model/Dlna/StreamBuilder.cs | 305 ++++++++---------- MediaBrowser.Model/Dlna/StreamInfo.cs | 99 +++++- MediaBrowser.Model/Entities/MediaStream.cs | 19 ++ MediaBrowser.Model/MediaBrowser.Model.csproj | 1 + .../SqliteMediaStreamsRepository.cs | 47 ++- 46 files changed, 1146 insertions(+), 287 deletions(-) create mode 100644 MediaBrowser.Model/Dlna/ConditionProcessor.cs diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 93f3e2d9f9..a4abb129b3 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -1665,8 +1665,21 @@ namespace MediaBrowser.Api.Playback } var mediaProfile = state.VideoRequest == null ? - profile.GetAudioMediaProfile(state.OutputContainer, audioCodec) : - profile.GetVideoMediaProfile(state.OutputContainer, audioCodec, videoCodec); + profile.GetAudioMediaProfile(state.OutputContainer, audioCodec, state.OutputAudioChannels, state.OutputAudioBitrate) : + profile.GetVideoMediaProfile(state.OutputContainer, + audioCodec, + videoCodec, + state.OutputAudioBitrate, + state.OutputAudioChannels, + state.OutputWidth, + state.OutputHeight, + state.TargetVideoBitDepth, + state.OutputVideoBitrate, + state.TargetVideoProfile, + state.TargetVideoLevel, + state.TargetFramerate, + state.TargetPacketLength, + state.TargetTimestamp); if (mediaProfile != null) { @@ -1752,10 +1765,17 @@ namespace MediaBrowser.Api.Playback audioCodec, state.OutputWidth, state.OutputHeight, - state.TotalOutputBitrate, - TransportStreamTimestamp.VALID, + state.TargetVideoBitDepth, + state.OutputVideoBitrate, + state.OutputAudioBitrate, + state.OutputAudioChannels, + state.TargetTimestamp, isStaticallyStreamed, state.RunTimeTicks, + state.TargetVideoProfile, + state.TargetVideoLevel, + state.TargetFramerate, + state.TargetPacketLength, state.TranscodeSeekInfo ); } diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs index 8a8246b69d..fe9199244b 100644 --- a/MediaBrowser.Api/Playback/StreamState.cs +++ b/MediaBrowser.Api/Playback/StreamState.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Common.Net; +using System.Globalization; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Drawing; @@ -221,5 +222,88 @@ namespace MediaBrowser.Api.Playback return VideoRequest.MaxHeight ?? VideoRequest.Height; } } + + /// + /// Predicts the audio sample rate that will be in the output stream + /// + public int? TargetVideoBitDepth + { + get + { + var stream = VideoStream; + return stream == null || !Request.Static ? null : stream.BitDepth; + } + } + + /// + /// Predicts the audio sample rate that will be in the output stream + /// + public double? TargetFramerate + { + get + { + var stream = VideoStream; + var requestedFramerate = VideoRequest.MaxFramerate ?? VideoRequest.Framerate; + + return requestedFramerate.HasValue && !Request.Static + ? requestedFramerate + : stream == null ? null : stream.AverageFrameRate ?? stream.RealFrameRate; + } + } + + /// + /// Predicts the audio sample rate that will be in the output stream + /// + public double? TargetVideoLevel + { + get + { + var stream = VideoStream; + return !string.IsNullOrEmpty(VideoRequest.Level) && !Request.Static + ? double.Parse(VideoRequest.Level, CultureInfo.InvariantCulture) + : stream == null ? null : stream.Level; + } + } + + public TransportStreamTimestamp TargetTimestamp + { + get + { + var stream = VideoStream; + + return !Request.Static + ? TransportStreamTimestamp.VALID + : stream == null ? TransportStreamTimestamp.VALID : stream.Timestamp; + } + } + + /// + /// Predicts the audio sample rate that will be in the output stream + /// + public int? TargetPacketLength + { + get + { + var stream = VideoStream; + return !Request.Static + ? null + : stream == null ? null : stream.PacketLength; + } + } + + /// + /// Predicts the audio sample rate that will be in the output stream + /// + public string TargetVideoProfile + { + get + { + var stream = VideoStream; + return !string.IsNullOrEmpty(VideoRequest.Profile) && !Request.Static + ? VideoRequest.Profile + : stream == null ? null : stream.Profile; + } + } + } } diff --git a/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs b/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs index 6ef22ef5b8..521bf7a817 100644 --- a/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs +++ b/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs @@ -195,6 +195,8 @@ namespace MediaBrowser.Controller.MediaEncoding stream.AverageFrameRate = GetFrameRate(streamInfo.avg_frame_rate); stream.RealFrameRate = GetFrameRate(streamInfo.r_frame_rate); + + stream.BitDepth = GetBitDepth(stream.PixelFormat); } else { @@ -232,6 +234,34 @@ namespace MediaBrowser.Controller.MediaEncoding return stream; } + private static int? GetBitDepth(string pixelFormat) + { + var eightBit = new List + { + "yuv420p", + "yuv411p", + "yuvj420p", + "uyyvyy411", + "nv12", + "nv21", + "rgb444le", + "rgb444be", + "bgr444le", + "bgr444be", + "yuvj411p" + }; + + if (!string.IsNullOrEmpty(pixelFormat)) + { + if (eightBit.Contains(pixelFormat, StringComparer.OrdinalIgnoreCase)) + { + return 8; + } + } + + return null; + } + /// /// Gets a string from an FFProbeResult tags dictionary /// diff --git a/MediaBrowser.Dlna/Didl/DidlBuilder.cs b/MediaBrowser.Dlna/Didl/DidlBuilder.cs index 27aae7d9c1..4209e0f156 100644 --- a/MediaBrowser.Dlna/Didl/DidlBuilder.cs +++ b/MediaBrowser.Dlna/Didl/DidlBuilder.cs @@ -9,7 +9,6 @@ using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Entities; using System; -using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Xml; @@ -47,6 +46,12 @@ namespace MediaBrowser.Dlna.Didl didl.SetAttribute("xmlns:dlna", NS_DLNA); didl.SetAttribute("xmlns:upnp", NS_UPNP); //didl.SetAttribute("xmlns:sec", NS_SEC); + + foreach (var att in _profile.ContentDirectoryRootAttributes) + { + didl.SetAttribute(att.Name, att.Value); + } + result.AppendChild(didl); result.DocumentElement.AppendChild(GetItemElement(result, item, deviceId, filter)); @@ -130,7 +135,7 @@ namespace MediaBrowser.Dlna.Didl } } - var totalBitrate = streamInfo.TotalOutputBitrate; + var totalBitrate = streamInfo.TargetTotalBitrate; var targetSampleRate = streamInfo.TargetAudioSampleRate; var targetChannels = streamInfo.TargetAudioChannels; @@ -162,7 +167,18 @@ namespace MediaBrowser.Dlna.Didl var mediaProfile = _profile.GetVideoMediaProfile(streamInfo.Container, streamInfo.AudioCodec, - streamInfo.VideoCodec); + streamInfo.VideoCodec, + streamInfo.TargetAudioBitrate, + targetChannels, + targetWidth, + targetHeight, + streamInfo.TargetVideoBitDepth, + streamInfo.TargetVideoBitrate, + streamInfo.TargetVideoProfile, + streamInfo.TargetVideoLevel, + streamInfo.TargetFramerate, + streamInfo.TargetPacketLength, + streamInfo.TargetTimestamp); var filename = url.Substring(0, url.IndexOf('?')); @@ -175,10 +191,17 @@ namespace MediaBrowser.Dlna.Didl streamInfo.AudioCodec, targetWidth, targetHeight, - totalBitrate, + streamInfo.TargetVideoBitDepth, + streamInfo.TargetVideoBitrate, + streamInfo.TargetAudioChannels, + streamInfo.TargetAudioBitrate, streamInfo.TargetTimestamp, streamInfo.IsDirectStream, streamInfo.RunTimeTicks, + streamInfo.TargetVideoProfile, + streamInfo.TargetVideoLevel, + streamInfo.TargetFramerate, + streamInfo.TargetPacketLength, streamInfo.TranscodeSeekInfo); res.SetAttribute("protocolInfo", String.Format( @@ -248,7 +271,9 @@ namespace MediaBrowser.Dlna.Didl } var mediaProfile = _profile.GetAudioMediaProfile(streamInfo.Container, - streamInfo.AudioCodec); + streamInfo.AudioCodec, + targetChannels, + targetAudioBitrate); var filename = url.Substring(0, url.IndexOf('?')); @@ -541,15 +566,12 @@ namespace MediaBrowser.Dlna.Didl var width = albumartUrlInfo.Width; var height = albumartUrlInfo.Height; - var mediaProfile = new MediaFormatProfileResolver().ResolveImageFormat("jpg", width, height); + var contentFeatures = new ContentFeatureBuilder(_profile).BuildImageHeader("jpg", width, height); - var orgPn = mediaProfile.HasValue ? "DLNA.ORG_PN=:" + mediaProfile.Value + ";" : string.Empty; - - res.SetAttribute("protocolInfo", string.Format( - "http-get:*:{1}:{0}DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS={2}", - orgPn, + res.SetAttribute("protocolInfo", String.Format( + "http-get:*:{0}:{1}", "image/jpeg", - DlnaMaps.DefaultStreaming + contentFeatures )); if (width.HasValue && height.HasValue) diff --git a/MediaBrowser.Dlna/PlayTo/DlnaController.cs b/MediaBrowser.Dlna/PlayTo/DlnaController.cs index 28fc4474e9..6b6152d85a 100644 --- a/MediaBrowser.Dlna/PlayTo/DlnaController.cs +++ b/MediaBrowser.Dlna/PlayTo/DlnaController.cs @@ -428,10 +428,17 @@ namespace MediaBrowser.Dlna.PlayTo streamInfo.AudioCodec, streamInfo.TargetWidth, streamInfo.TargetHeight, - streamInfo.TotalOutputBitrate, + streamInfo.TargetVideoBitDepth, + streamInfo.TargetVideoBitrate, + streamInfo.TargetAudioChannels, + streamInfo.TargetAudioBitrate, streamInfo.TargetTimestamp, streamInfo.IsDirectStream, streamInfo.RunTimeTicks, + streamInfo.TargetVideoProfile, + streamInfo.TargetVideoLevel, + streamInfo.TargetFramerate, + streamInfo.TargetPacketLength, streamInfo.TranscodeSeekInfo); } diff --git a/MediaBrowser.Dlna/Profiles/PanasonicVieraProfile.cs b/MediaBrowser.Dlna/Profiles/PanasonicVieraProfile.cs index 178207fc6a..2968bcd5fa 100644 --- a/MediaBrowser.Dlna/Profiles/PanasonicVieraProfile.cs +++ b/MediaBrowser.Dlna/Profiles/PanasonicVieraProfile.cs @@ -1,6 +1,5 @@ -using System.Xml.Serialization; -using MediaBrowser.Controller.Dlna; -using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Dlna; +using System.Xml.Serialization; namespace MediaBrowser.Dlna.Profiles { @@ -27,6 +26,15 @@ namespace MediaBrowser.Dlna.Profiles } }; + ContentDirectoryRootAttributes = new[] + { + new XmlAttribute + { + Name = "xmlns:pv", + Value = "http://www.pv.com/pvns/" + } + }; + TimelineOffsetSeconds = 10; TranscodingProfiles = new[] diff --git a/MediaBrowser.Dlna/Profiles/SamsungSmartTvProfile.cs b/MediaBrowser.Dlna/Profiles/SamsungSmartTvProfile.cs index 9b113b6c66..69203f3425 100644 --- a/MediaBrowser.Dlna/Profiles/SamsungSmartTvProfile.cs +++ b/MediaBrowser.Dlna/Profiles/SamsungSmartTvProfile.cs @@ -17,6 +17,15 @@ namespace MediaBrowser.Dlna.Profiles ModelUrl = "samsung.com" }; + ContentDirectoryRootAttributes = new[] + { + new XmlAttribute + { + Name = "xmlns:sec", + Value = "http://www.sec.co.kr/" + } + }; + TranscodingProfiles = new[] { new TranscodingProfile diff --git a/MediaBrowser.Dlna/Profiles/SonyBlurayPlayer2013Profile.cs b/MediaBrowser.Dlna/Profiles/SonyBlurayPlayer2013Profile.cs index 833f623bf4..bc6ac98ca5 100644 --- a/MediaBrowser.Dlna/Profiles/SonyBlurayPlayer2013Profile.cs +++ b/MediaBrowser.Dlna/Profiles/SonyBlurayPlayer2013Profile.cs @@ -18,6 +18,15 @@ namespace MediaBrowser.Dlna.Profiles ModelNumber = "BDP-2013" }; + ContentDirectoryRootAttributes = new[] + { + new XmlAttribute + { + Name = "xmlns:av", + Value = "urn:schemas-sony-com:av" + } + }; + ModelName = "Windows Media Player Sharing"; ModelNumber = "3.0"; Manufacturer = "Microsoft Corporation"; diff --git a/MediaBrowser.Dlna/Profiles/SonyBlurayPlayerProfile.cs b/MediaBrowser.Dlna/Profiles/SonyBlurayPlayerProfile.cs index 970c57319a..bb5a109da2 100644 --- a/MediaBrowser.Dlna/Profiles/SonyBlurayPlayerProfile.cs +++ b/MediaBrowser.Dlna/Profiles/SonyBlurayPlayerProfile.cs @@ -33,6 +33,15 @@ namespace MediaBrowser.Dlna.Profiles } }; + ContentDirectoryRootAttributes = new[] + { + new XmlAttribute + { + Name = "xmlns:av", + Value = "urn:schemas-sony-com:av" + } + }; + ModelName = "Windows Media Player Sharing"; ModelNumber = "3.0"; Manufacturer = "Microsoft Corporation"; diff --git a/MediaBrowser.Dlna/Profiles/SonyBravia2010Profile.cs b/MediaBrowser.Dlna/Profiles/SonyBravia2010Profile.cs index 6639e18763..d3c0ae7a78 100644 --- a/MediaBrowser.Dlna/Profiles/SonyBravia2010Profile.cs +++ b/MediaBrowser.Dlna/Profiles/SonyBravia2010Profile.cs @@ -27,6 +27,15 @@ namespace MediaBrowser.Dlna.Profiles } }; + ContentDirectoryRootAttributes = new[] + { + new XmlAttribute + { + Name = "xmlns:av", + Value = "urn:schemas-sony-com:av" + } + }; + ModelName = "Windows Media Player Sharing"; ModelNumber = "3.0"; ModelUrl = "http://www.microsoft.com/"; @@ -99,7 +108,23 @@ namespace MediaBrowser.Dlna.Profiles AudioCodec="ac3,aac,mp3", MimeType = "video/vnd.dlna.mpeg-tts", OrgPn="AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T", - Type = DlnaProfileType.Video + Type = DlnaProfileType.Video, + + Conditions = new [] + { + new ProfileCondition + { + Condition = ProfileConditionType.Equals, + Property = ProfileConditionValue.PacketLength, + Value = "192" + }, + new ProfileCondition + { + Condition = ProfileConditionType.Equals, + Property = ProfileConditionValue.VideoTimestamp, + Value = "Valid" + } + } }, new ResponseProfile @@ -109,7 +134,17 @@ namespace MediaBrowser.Dlna.Profiles AudioCodec="ac3,aac,mp3", MimeType = "video/mpeg", OrgPn="AVC_TS_HD_24_AC3_ISO,AVC_TS_HD_50_AC3_ISO,AVC_TS_HD_60_AC3_ISO,AVC_TS_HD_EU_ISO", - Type = DlnaProfileType.Video + Type = DlnaProfileType.Video, + + Conditions = new [] + { + new ProfileCondition + { + Condition = ProfileConditionType.Equals, + Property = ProfileConditionValue.PacketLength, + Value = "188" + } + } }, new ResponseProfile diff --git a/MediaBrowser.Dlna/Profiles/SonyBravia2011Profile.cs b/MediaBrowser.Dlna/Profiles/SonyBravia2011Profile.cs index 8665b892ab..65a3cdb0dc 100644 --- a/MediaBrowser.Dlna/Profiles/SonyBravia2011Profile.cs +++ b/MediaBrowser.Dlna/Profiles/SonyBravia2011Profile.cs @@ -1,6 +1,5 @@ -using System.Xml.Serialization; -using MediaBrowser.Controller.Dlna; -using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Dlna; +using System.Xml.Serialization; namespace MediaBrowser.Dlna.Profiles { @@ -27,6 +26,15 @@ namespace MediaBrowser.Dlna.Profiles } }; + ContentDirectoryRootAttributes = new[] + { + new XmlAttribute + { + Name = "xmlns:av", + Value = "urn:schemas-sony-com:av" + } + }; + ModelName = "Windows Media Player Sharing"; ModelNumber = "3.0"; ModelUrl = "http://www.microsoft.com/"; @@ -141,7 +149,23 @@ namespace MediaBrowser.Dlna.Profiles AudioCodec="ac3,aac,mp3", MimeType = "video/vnd.dlna.mpeg-tts", OrgPn="AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T", - Type = DlnaProfileType.Video + Type = DlnaProfileType.Video, + + Conditions = new [] + { + new ProfileCondition + { + Condition = ProfileConditionType.Equals, + Property = ProfileConditionValue.PacketLength, + Value = "192" + }, + new ProfileCondition + { + Condition = ProfileConditionType.Equals, + Property = ProfileConditionValue.VideoTimestamp, + Value = "Valid" + } + } }, new ResponseProfile @@ -151,7 +175,17 @@ namespace MediaBrowser.Dlna.Profiles AudioCodec="ac3,aac,mp3", MimeType = "video/mpeg", OrgPn="AVC_TS_HD_24_AC3_ISO,AVC_TS_HD_50_AC3_ISO,AVC_TS_HD_60_AC3_ISO,AVC_TS_HD_EU_ISO", - Type = DlnaProfileType.Video + Type = DlnaProfileType.Video, + + Conditions = new [] + { + new ProfileCondition + { + Condition = ProfileConditionType.Equals, + Property = ProfileConditionValue.PacketLength, + Value = "188" + } + } }, new ResponseProfile diff --git a/MediaBrowser.Dlna/Profiles/SonyBravia2012Profile.cs b/MediaBrowser.Dlna/Profiles/SonyBravia2012Profile.cs index 5becc6752a..5a818d0adc 100644 --- a/MediaBrowser.Dlna/Profiles/SonyBravia2012Profile.cs +++ b/MediaBrowser.Dlna/Profiles/SonyBravia2012Profile.cs @@ -27,6 +27,15 @@ namespace MediaBrowser.Dlna.Profiles } }; + ContentDirectoryRootAttributes = new[] + { + new XmlAttribute + { + Name = "xmlns:av", + Value = "urn:schemas-sony-com:av" + } + }; + ModelName = "Windows Media Player Sharing"; ModelNumber = "3.0"; ModelUrl = "http://www.microsoft.com/"; @@ -129,7 +138,23 @@ namespace MediaBrowser.Dlna.Profiles AudioCodec="ac3,aac,mp3", MimeType = "video/vnd.dlna.mpeg-tts", OrgPn="AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T", - Type = DlnaProfileType.Video + Type = DlnaProfileType.Video, + + Conditions = new [] + { + new ProfileCondition + { + Condition = ProfileConditionType.Equals, + Property = ProfileConditionValue.PacketLength, + Value = "192" + }, + new ProfileCondition + { + Condition = ProfileConditionType.Equals, + Property = ProfileConditionValue.VideoTimestamp, + Value = "Valid" + } + } }, new ResponseProfile @@ -139,7 +164,17 @@ namespace MediaBrowser.Dlna.Profiles AudioCodec="ac3,aac,mp3", MimeType = "video/mpeg", OrgPn="AVC_TS_HD_24_AC3_ISO,AVC_TS_HD_50_AC3_ISO,AVC_TS_HD_60_AC3_ISO,AVC_TS_HD_EU_ISO", - Type = DlnaProfileType.Video + Type = DlnaProfileType.Video, + + Conditions = new [] + { + new ProfileCondition + { + Condition = ProfileConditionType.Equals, + Property = ProfileConditionValue.PacketLength, + Value = "188" + } + } }, new ResponseProfile diff --git a/MediaBrowser.Dlna/Profiles/SonyBravia2013Profile.cs b/MediaBrowser.Dlna/Profiles/SonyBravia2013Profile.cs index 51ded765eb..5923bce4cf 100644 --- a/MediaBrowser.Dlna/Profiles/SonyBravia2013Profile.cs +++ b/MediaBrowser.Dlna/Profiles/SonyBravia2013Profile.cs @@ -26,6 +26,15 @@ namespace MediaBrowser.Dlna.Profiles } }; + ContentDirectoryRootAttributes = new[] + { + new XmlAttribute + { + Name = "xmlns:av", + Value = "urn:schemas-sony-com:av" + } + }; + ModelName = "Windows Media Player Sharing"; ModelNumber = "3.0"; ModelUrl = "http://www.microsoft.com/"; @@ -184,7 +193,23 @@ namespace MediaBrowser.Dlna.Profiles AudioCodec="ac3,aac,mp3", MimeType = "video/vnd.dlna.mpeg-tts", OrgPn="AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T", - Type = DlnaProfileType.Video + Type = DlnaProfileType.Video, + + Conditions = new [] + { + new ProfileCondition + { + Condition = ProfileConditionType.Equals, + Property = ProfileConditionValue.PacketLength, + Value = "192" + }, + new ProfileCondition + { + Condition = ProfileConditionType.Equals, + Property = ProfileConditionValue.VideoTimestamp, + Value = "Valid" + } + } }, new ResponseProfile @@ -194,7 +219,17 @@ namespace MediaBrowser.Dlna.Profiles AudioCodec="ac3,aac,mp3", MimeType = "video/mpeg", OrgPn="AVC_TS_HD_24_AC3_ISO,AVC_TS_HD_50_AC3_ISO,AVC_TS_HD_60_AC3_ISO,AVC_TS_HD_EU_ISO", - Type = DlnaProfileType.Video + Type = DlnaProfileType.Video, + + Conditions = new [] + { + new ProfileCondition + { + Condition = ProfileConditionType.Equals, + Property = ProfileConditionValue.PacketLength, + Value = "188" + } + } }, new ResponseProfile diff --git a/MediaBrowser.Dlna/Profiles/Xml/Default.xml b/MediaBrowser.Dlna/Profiles/Xml/Default.xml index dcc8ede744..e9b920d171 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Default.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Default.xml @@ -22,6 +22,7 @@ 0 false false + diff --git a/MediaBrowser.Dlna/Profiles/Xml/Denon AVR.xml b/MediaBrowser.Dlna/Profiles/Xml/Denon AVR.xml index 3b7f3f74d4..7ee3abdde1 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Denon AVR.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Denon AVR.xml @@ -27,6 +27,7 @@ 0 false false + diff --git a/MediaBrowser.Dlna/Profiles/Xml/LG Smart TV.xml b/MediaBrowser.Dlna/Profiles/Xml/LG Smart TV.xml index 92a107c6e4..aec6d2cc1b 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/LG Smart TV.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/LG Smart TV.xml @@ -28,6 +28,7 @@ 10 false false + diff --git a/MediaBrowser.Dlna/Profiles/Xml/Linksys DMA2100.xml b/MediaBrowser.Dlna/Profiles/Xml/Linksys DMA2100.xml index 00d3d3a09e..a0a4f502cf 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Linksys DMA2100.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Linksys DMA2100.xml @@ -26,6 +26,7 @@ 0 false false + diff --git a/MediaBrowser.Dlna/Profiles/Xml/Panasonic Viera.xml b/MediaBrowser.Dlna/Profiles/Xml/Panasonic Viera.xml index dfb95c036a..4f8ae88e35 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Panasonic Viera.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Panasonic Viera.xml @@ -29,6 +29,9 @@ 10 false false + + + diff --git a/MediaBrowser.Dlna/Profiles/Xml/Samsung Smart TV.xml b/MediaBrowser.Dlna/Profiles/Xml/Samsung Smart TV.xml index 4e0f5e623c..1371710cc7 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Samsung Smart TV.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Samsung Smart TV.xml @@ -26,6 +26,9 @@ 0 false false + + + diff --git a/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2013.xml b/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2013.xml index c9463225c4..86ced11506 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2013.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2013.xml @@ -28,6 +28,9 @@ 0 false false + + + diff --git a/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player.xml b/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player.xml index 1805d81b44..22b3b2ce50 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player.xml @@ -30,6 +30,9 @@ 0 false false + + + diff --git a/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2010).xml b/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2010).xml index 42b866bd32..40605bc4e2 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2010).xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2010).xml @@ -30,6 +30,9 @@ 0 false false + + + @@ -83,10 +86,15 @@ - + + + + - + + + diff --git a/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2011).xml b/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2011).xml index 073996ccbf..561bd2c00e 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2011).xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2011).xml @@ -30,6 +30,9 @@ 0 false false + + + @@ -86,10 +89,15 @@ - + + + + - + + + diff --git a/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2012).xml b/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2012).xml index d1bf59991d..b62b60a66b 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2012).xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2012).xml @@ -30,6 +30,9 @@ 0 false false + + + @@ -69,10 +72,15 @@ - + + + + - + + + diff --git a/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2013).xml b/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2013).xml index 4dc1d9a741..ea78552c68 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2013).xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2013).xml @@ -30,6 +30,9 @@ 0 false false + + + @@ -69,10 +72,15 @@ - + + + + - + + + diff --git a/MediaBrowser.Dlna/Profiles/Xml/Sony PlayStation 3.xml b/MediaBrowser.Dlna/Profiles/Xml/Sony PlayStation 3.xml index fed31fec0e..f27f5aa4a6 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Sony PlayStation 3.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Sony PlayStation 3.xml @@ -30,6 +30,7 @@ 0 false false + diff --git a/MediaBrowser.Dlna/Profiles/Xml/WDTV Live.xml b/MediaBrowser.Dlna/Profiles/Xml/WDTV Live.xml index 0936a6ced2..bee389b69f 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/WDTV Live.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/WDTV Live.xml @@ -29,6 +29,7 @@ 5 false false + diff --git a/MediaBrowser.Dlna/Profiles/Xml/Xbox 360.xml b/MediaBrowser.Dlna/Profiles/Xml/Xbox 360.xml index cf12b26405..c72f404396 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Xbox 360.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Xbox 360.xml @@ -29,6 +29,7 @@ 40 true true + diff --git a/MediaBrowser.Dlna/Profiles/Xml/Xbox One.xml b/MediaBrowser.Dlna/Profiles/Xml/Xbox One.xml index a1d3ed428e..c2aae9ef4b 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Xbox One.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Xbox One.xml @@ -27,6 +27,7 @@ 0 false false + diff --git a/MediaBrowser.Dlna/Profiles/Xml/foobar2000.xml b/MediaBrowser.Dlna/Profiles/Xml/foobar2000.xml index 2269af5d46..a80dff73e9 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/foobar2000.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/foobar2000.xml @@ -28,6 +28,7 @@ 0 false false + diff --git a/MediaBrowser.Dlna/Server/ControlHandler.cs b/MediaBrowser.Dlna/Server/ControlHandler.cs index 734adf7952..b28ec010e8 100644 --- a/MediaBrowser.Dlna/Server/ControlHandler.cs +++ b/MediaBrowser.Dlna/Server/ControlHandler.cs @@ -40,6 +40,8 @@ namespace MediaBrowser.Dlna.Server private readonly DidlBuilder _didlBuilder; + private readonly DeviceProfile _profile; + public ControlHandler(ILogger logger, ILibraryManager libraryManager, DeviceProfile profile, string serverAddress, IDtoService dtoService, IImageProcessor imageProcessor, IUserDataManager userDataManager, User user, int systemUpdateId) { _logger = logger; @@ -47,6 +49,7 @@ namespace MediaBrowser.Dlna.Server _userDataManager = userDataManager; _user = user; _systemUpdateId = systemUpdateId; + _profile = profile; _didlBuilder = new DidlBuilder(profile, imageProcessor, serverAddress, dtoService); } @@ -314,7 +317,6 @@ namespace MediaBrowser.Dlna.Server // sort example: dc:title, dc:date - var provided = 0; var requested = 0; var start = 0; @@ -334,7 +336,12 @@ namespace MediaBrowser.Dlna.Server didl.SetAttribute("xmlns:dc", NS_DC); didl.SetAttribute("xmlns:dlna", NS_DLNA); didl.SetAttribute("xmlns:upnp", NS_UPNP); - //didl.SetAttribute("xmlns:sec", NS_SEC); + + foreach (var att in _profile.ContentDirectoryRootAttributes) + { + didl.SetAttribute(att.Name, att.Value); + } + result.AppendChild(didl); var folder = (Folder)GetItemFromObjectId(sparams["ContainerID"], user); @@ -352,7 +359,7 @@ namespace MediaBrowser.Dlna.Server children = children.Take(requested).ToList(); } - provided = children.Count; + var provided = children.Count; foreach (var i in children) { diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj index fe2fe20917..56baaec0a6 100644 --- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj +++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj @@ -104,6 +104,9 @@ Dlna\CodecProfile.cs + + Dlna\ConditionProcessor.cs + Dlna\ContainerProfile.cs diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj index b833a19a70..37fb68ad6d 100644 --- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj +++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj @@ -91,6 +91,9 @@ Dlna\CodecProfile.cs + + Dlna\ConditionProcessor.cs + Dlna\ContainerProfile.cs 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 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; } + /// /// Gets or sets the direct play profiles. /// @@ -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 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 { val.Value } : new List(); + } 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 { val.Value } : new List(); + } 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 { val.Value } : new List(); + } 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(); } private IEnumerable 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(); @@ -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; + playlistItem.IsDirectStream = true; + playlistItem.Container = item.Container; - // 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; - - 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 conditions, string mediaPath, MediaStream videoStream, MediaStream audioStream) - { - return conditions.All(i => IsConditionSatisfied(i, mediaPath, videoStream, audioStream)); - } - - /// - /// Determines whether [is condition satisfied] [the specified condition]. - /// - /// The condition. - /// The media path. - /// The video stream. - /// The audio stream. - /// true if [is condition satisfied] [the specified condition]; otherwise, false. - /// Unexpected ProfileConditionType - 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; - } - - /// - /// Gets the condition value. - /// - /// The condition. - /// The media path. - /// The video stream. - /// The audio stream. - /// System.Nullable{System.Int64}. - /// Unexpected Property - 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 - { - "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 @@ -177,6 +176,74 @@ namespace MediaBrowser.Model.Dlna } } + /// + /// Predicts the audio sample rate that will be in the output stream + /// + public int? TargetVideoBitDepth + { + get + { + var stream = TargetVideoStream; + return stream == null || !IsDirectStream ? null : stream.BitDepth; + } + } + + /// + /// Predicts the audio sample rate that will be in the output stream + /// + public double? TargetFramerate + { + get + { + var stream = TargetVideoStream; + return MaxFramerate.HasValue && !IsDirectStream + ? MaxFramerate + : stream == null ? null : stream.AverageFrameRate ?? stream.RealFrameRate; + } + } + + /// + /// Predicts the audio sample rate that will be in the output stream + /// + public double? TargetVideoLevel + { + get + { + var stream = TargetVideoStream; + return VideoLevel.HasValue && !IsDirectStream + ? VideoLevel + : stream == null ? null : stream.Level; + } + } + + /// + /// Predicts the audio sample rate that will be in the output stream + /// + public int? TargetPacketLength + { + get + { + var stream = TargetVideoStream; + return !IsDirectStream + ? null + : stream == null ? null : stream.PacketLength; + } + } + + /// + /// Predicts the audio sample rate that will be in the output stream + /// + public string TargetVideoProfile + { + get + { + var stream = TargetVideoStream; + return !string.IsNullOrEmpty(VideoProfile) && !IsDirectStream + ? VideoProfile + : stream == null ? null : stream.Profile; + } + } + /// /// Predicts the audio bitrate that will be in the output stream /// @@ -256,11 +323,35 @@ namespace MediaBrowser.Model.Dlna } } - public int? TotalOutputBitrate + public int? TargetVideoBitrate { get { - return (TargetAudioBitrate ?? 0) + (VideoBitrate ?? 0); + 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) + (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 { @@ -39,6 +40,24 @@ namespace MediaBrowser.Model.Entities /// The bit rate. public int? BitRate { get; set; } + /// + /// Gets or sets the bit depth. + /// + /// The bit depth. + public int? BitDepth { get; set; } + + /// + /// Gets or sets the length of the packet. + /// + /// The length of the packet. + public int? PacketLength { get; set; } + + /// + /// Gets or sets the timestamp. + /// + /// The timestamp. + public TransportStreamTimestamp Timestamp { get; set; } + /// /// Gets or sets the channels. /// 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 @@ + diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteMediaStreamsRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteMediaStreamsRepository.cs index 3a8aeb6e69..58ca432bbd 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteMediaStreamsRepository.cs +++ b/MediaBrowser.Server.Implementations/Persistence/SqliteMediaStreamsRepository.cs @@ -1,11 +1,11 @@ -using System.Text; -using MediaBrowser.Controller.Persistence; +using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using System; using System.Collections.Generic; using System.Data; using System.Linq; +using System.Text; using System.Threading; using System.Threading.Tasks; @@ -40,7 +40,7 @@ namespace MediaBrowser.Server.Implementations.Persistence // Add PixelFormat column - createTableCommand += "(ItemId GUID, StreamIndex INT, StreamType TEXT, Codec TEXT, Language TEXT, ChannelLayout TEXT, Profile TEXT, AspectRatio TEXT, Path TEXT, IsInterlaced BIT, BitRate INT NULL, Channels INT NULL, SampleRate INT NULL, IsDefault BIT, IsForced BIT, IsExternal BIT, Height INT NULL, Width INT NULL, AverageFrameRate FLOAT NULL, RealFrameRate FLOAT NULL, Level FLOAT NULL, PixelFormat TEXT, PRIMARY KEY (ItemId, StreamIndex))"; + createTableCommand += "(ItemId GUID, StreamIndex INT, StreamType TEXT, Codec TEXT, Language TEXT, ChannelLayout TEXT, Profile TEXT, AspectRatio TEXT, Path TEXT, IsInterlaced BIT, BitRate INT NULL, Channels INT NULL, SampleRate INT NULL, IsDefault BIT, IsForced BIT, IsExternal BIT, Height INT NULL, Width INT NULL, AverageFrameRate FLOAT NULL, RealFrameRate FLOAT NULL, Level FLOAT NULL, PixelFormat TEXT, BitDepth INT NULL, PRIMARY KEY (ItemId, StreamIndex))"; string[] queries = { @@ -57,6 +57,7 @@ namespace MediaBrowser.Server.Implementations.Persistence _connection.RunQueries(queries, _logger); AddPixelFormatColumnCommand(); + AddBitDepthCommand(); PrepareStatements(); @@ -94,6 +95,37 @@ namespace MediaBrowser.Server.Implementations.Persistence _connection.RunQueries(new[] { builder.ToString() }, _logger); } + private void AddBitDepthCommand() + { + using (var cmd = _connection.CreateCommand()) + { + cmd.CommandText = "PRAGMA table_info(mediastreams)"; + + using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult)) + { + while (reader.Read()) + { + if (!reader.IsDBNull(1)) + { + var name = reader.GetString(1); + + if (string.Equals(name, "BitDepth", StringComparison.OrdinalIgnoreCase)) + { + return; + } + } + } + } + } + + var builder = new StringBuilder(); + + builder.AppendLine("alter table mediastreams"); + builder.AppendLine("add column BitDepth INT NULL"); + + _connection.RunQueries(new[] { builder.ToString() }, _logger); + } + private readonly string[] _saveColumns = { "ItemId", @@ -117,7 +149,8 @@ namespace MediaBrowser.Server.Implementations.Persistence "AverageFrameRate", "RealFrameRate", "Level", - "PixelFormat" + "PixelFormat", + "BitDepth" }; /// @@ -281,6 +314,11 @@ namespace MediaBrowser.Server.Implementations.Persistence item.PixelFormat = reader.GetString(21); } + if (!reader.IsDBNull(22)) + { + item.BitDepth = reader.GetInt32(22); + } + return item; } @@ -343,6 +381,7 @@ namespace MediaBrowser.Server.Implementations.Persistence _saveStreamCommand.GetParameter(19).Value = stream.RealFrameRate; _saveStreamCommand.GetParameter(20).Value = stream.Level; _saveStreamCommand.GetParameter(21).Value = stream.PixelFormat; + _saveStreamCommand.GetParameter(22).Value = stream.BitDepth; _saveStreamCommand.Transaction = transaction; _saveStreamCommand.ExecuteNonQuery();