Przeglądaj źródła

Normalize orgPn usage

Luke Pulverenti 11 lat temu
rodzic
commit
44bfad70d2

+ 53 - 77
MediaBrowser.Api/Playback/BaseStreamingService.cs

@@ -1507,6 +1507,16 @@ namespace MediaBrowser.Api.Playback
                 }
             }
 
+            var headers = new Dictionary<string, string>();
+            foreach (var key in Request.Headers.AllKeys)
+            {
+                headers[key] = Request.Headers[key];
+            }
+
+            state.DeviceProfile = string.IsNullOrWhiteSpace(state.Request.DeviceProfileId) ?
+                DlnaManager.GetProfile(headers) :
+                DlnaManager.GetProfile(state.Request.DeviceProfileId); 
+            
             return state;
         }
 
@@ -1631,16 +1641,7 @@ namespace MediaBrowser.Api.Playback
 
         private void ApplyDeviceProfileSettings(StreamState state)
         {
-            var headers = new Dictionary<string, string>();
-
-            foreach (var key in Request.Headers.AllKeys)
-            {
-                headers[key] = Request.Headers[key];
-            }
-
-            var profile = string.IsNullOrWhiteSpace(state.Request.DeviceProfileId) ?
-                DlnaManager.GetProfile(headers) :
-                DlnaManager.GetProfile(state.Request.DeviceProfileId);
+            var profile = state.DeviceProfile;
 
             if (profile == null)
             {
@@ -1664,13 +1665,12 @@ namespace MediaBrowser.Api.Playback
             }
 
             var mediaProfile = state.VideoRequest == null ?
-                profile.GetAudioMediaProfile(state.OutputContainer, audioCodec, state.AudioStream) :
-                profile.GetVideoMediaProfile(state.OutputContainer, audioCodec, videoCodec, state.AudioStream, state.VideoStream);
+                profile.GetAudioMediaProfile(state.OutputContainer, audioCodec) :
+                profile.GetVideoMediaProfile(state.OutputContainer, audioCodec, videoCodec);
 
             if (mediaProfile != null)
             {
                 state.MimeType = mediaProfile.MimeType;
-                state.OrgPn = mediaProfile.OrgPn;
             }
 
             var transcodingProfile = state.VideoRequest == null ?
@@ -1699,95 +1699,71 @@ namespace MediaBrowser.Api.Playback
         /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
         protected void AddDlnaHeaders(StreamState state, IDictionary<string, string> responseHeaders, bool isStaticallyStreamed)
         {
+            var profile = state.DeviceProfile;
+
+            if (profile == null)
+            {
+                return;
+            }
+
             var transferMode = GetHeader("transferMode.dlna.org");
             responseHeaders["transferMode.dlna.org"] = string.IsNullOrEmpty(transferMode) ? "Streaming" : transferMode;
             responseHeaders["realTimeInfo.dlna.org"] = "DLNA.ORG_TLAG=*";
 
-            // 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
-            var orgOp = ";DLNA.ORG_OP=" + DlnaMaps.GetOrgOpValue(state.RunTimeTicks.HasValue, isStaticallyStreamed, state.TranscodeSeekInfo);
-
             if (state.RunTimeTicks.HasValue && !isStaticallyStreamed)
             {
                 AddTimeSeekResponseHeaders(state, responseHeaders);
             }
 
-            // 0 = native, 1 = transcoded
-            var orgCi = isStaticallyStreamed ? ";DLNA.ORG_CI=0" : ";DLNA.ORG_CI=1";
-
-            var flagValue = DlnaFlags.StreamingTransferMode |
-                            DlnaFlags.BackgroundTransferMode |
-                            DlnaFlags.DlnaV15;
-
-            if (isStaticallyStreamed)
-            {
-                //flagValue = flagValue | DlnaFlags.DLNA_ORG_FLAG_BYTE_BASED_SEEK;
-            }
-            else if (state.RunTimeTicks.HasValue)
-            {
-                //flagValue = flagValue | DlnaFlags.DLNA_ORG_FLAG_TIME_BASED_SEEK;
-            }
-
-            var dlnaflags = string.Format(";DLNA.ORG_FLAGS={0}000000000000000000000000",
-                Enum.Format(typeof(DlnaFlags), flagValue, "x"));
-
-            var orgPn = GetOrgPnValue(state);
-
-            var contentFeatures = string.IsNullOrEmpty(orgPn) ? string.Empty :
-                "DLNA.ORG_PN=" + orgPn;
-
-            if (!string.IsNullOrEmpty(contentFeatures))
-            {
-                responseHeaders["contentFeatures.dlna.org"] = (contentFeatures + orgOp + orgCi + dlnaflags).Trim(';');
-            }
-
-            foreach (var item in responseHeaders)
-            {
-                Request.Response.AddHeader(item.Key, item.Value);
-            }
-        }
-
-        private string GetOrgPnValue(StreamState state)
-        {
-            if (!string.IsNullOrWhiteSpace(state.OrgPn))
-            {
-                return state.OrgPn;
-            }
+            var audioCodec = state.Request.AudioCodec;
 
             if (state.VideoRequest == null)
             {
-                var format = new MediaFormatProfileResolver()
-                    .ResolveAudioFormat(state.OutputContainer,
+                responseHeaders["contentFeatures.dlna.org"] = new ContentFeatureBuilder(profile)
+                    .BuildAudioHeader(
+                    state.OutputContainer,
+                    audioCodec,
                     state.OutputAudioBitrate,
                     state.OutputAudioSampleRate,
-                    state.OutputAudioChannels);
-
-                return format.HasValue ? format.Value.ToString() : null;
+                    state.OutputAudioChannels,
+                    isStaticallyStreamed,
+                    state.RunTimeTicks,
+                    state.TranscodeSeekInfo
+                    );
             }
-
-            var audioCodec = state.Request.AudioCodec;
-
-            if (string.Equals(audioCodec, "copy", StringComparison.OrdinalIgnoreCase) && state.AudioStream != null)
+            else
             {
-                audioCodec = state.AudioStream.Codec;
-            }
+                if (string.Equals(audioCodec, "copy", StringComparison.OrdinalIgnoreCase) && state.AudioStream != null)
+                {
+                    audioCodec = state.AudioStream.Codec;
+                }
 
-            var videoCodec = state.VideoRequest == null ? null : state.VideoRequest.VideoCodec;
+                var videoCodec = state.VideoRequest == null ? null : state.VideoRequest.VideoCodec;
 
-            if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase) && state.VideoStream != null)
-            {
-                videoCodec = state.VideoStream.Codec;
-            }
+                if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase) && state.VideoStream != null)
+                {
+                    videoCodec = state.VideoStream.Codec;
+                }
 
-            var videoFormat = new MediaFormatProfileResolver()
-                .ResolveVideoFormat(state.OutputContainer,
+                responseHeaders["contentFeatures.dlna.org"] = new ContentFeatureBuilder(profile)
+                    .BuildVideoHeader(
+                    state.OutputContainer,
                     videoCodec,
                     audioCodec,
                     state.OutputWidth,
                     state.OutputHeight,
                     state.TotalOutputBitrate,
-                    TransportStreamTimestamp.VALID);
+                    TransportStreamTimestamp.VALID,
+                    isStaticallyStreamed,
+                    state.RunTimeTicks,
+                    state.TranscodeSeekInfo
+                    );
+            }
 
-            return videoFormat.HasValue ? videoFormat.Value.ToString() : null;
+            foreach (var item in responseHeaders)
+            {
+                Request.Response.AddHeader(item.Key, item.Value);
+            }
         }
 
         private void AddTimeSeekResponseHeaders(StreamState state, IDictionary<string, string> responseHeaders)

+ 2 - 1
MediaBrowser.Api/Playback/StreamState.cs

@@ -84,7 +84,6 @@ namespace MediaBrowser.Api.Playback
         public string InputAudioCodec { get; set; }
 
         public string MimeType { get; set; }
-        public string OrgPn { get; set; }
 
         public bool EstimateContentLength { get; set; }
         public bool EnableMpegtsM2TsMode { get; set; }
@@ -163,6 +162,8 @@ namespace MediaBrowser.Api.Playback
 
         public string OutputContainer { get; set; }
 
+        public DeviceProfile DeviceProfile { get; set; }
+
         public int? TotalOutputBitrate
         {
             get

+ 31 - 55
MediaBrowser.Dlna/PlayTo/DlnaController.cs

@@ -401,66 +401,38 @@ namespace MediaBrowser.Dlna.PlayTo
 
         private string GetDlnaHeaders(PlaylistItem item)
         {
+            var profile = item.Profile;
             var streamInfo = item.StreamInfo;
 
-            var orgOp = !streamInfo.IsDirectStream ? ";DLNA.ORG_OP=00" : ";DLNA.ORG_OP=01";
-
-            var orgCi = !streamInfo.IsDirectStream ? ";DLNA.ORG_CI=0" : ";DLNA.ORG_CI=1";
-
-            const string dlnaflags = ";DLNA.ORG_FLAGS=01500000000000000000000000000000";
-
-            string contentFeatures;
-
-            var container = streamInfo.Container.TrimStart('.');
-
-            if (string.Equals(container, "mp3", StringComparison.OrdinalIgnoreCase))
-            {
-                contentFeatures = "DLNA.ORG_PN=MP3";
-            }
-            else if (string.Equals(container, "wma", StringComparison.OrdinalIgnoreCase))
+            if (streamInfo.MediaType == DlnaProfileType.Audio)
             {
-                contentFeatures = "DLNA.ORG_PN=WMABASE";
+                return new ContentFeatureBuilder(profile)
+                    .BuildAudioHeader(streamInfo.Container,
+                    streamInfo.AudioCodec,
+                    streamInfo.TargetAudioBitrate,
+                    streamInfo.TargetAudioSampleRate,
+                    streamInfo.TargetAudioChannels,
+                    streamInfo.IsDirectStream,
+                    streamInfo.RunTimeTicks,
+                    streamInfo.TranscodeSeekInfo);
             }
-            else if (string.Equals(container, "wmw", StringComparison.OrdinalIgnoreCase))
-            {
-                contentFeatures = "DLNA.ORG_PN=WMVMED_BASE";
-            }
-            else if (string.Equals(container, "asf", StringComparison.OrdinalIgnoreCase))
-            {
-                contentFeatures = "DLNA.ORG_PN=WMVMED_BASE";
-            }
-            else if (string.Equals(container, "avi", StringComparison.OrdinalIgnoreCase))
-            {
-                contentFeatures = "DLNA.ORG_PN=AVI";
-            }
-            else if (string.Equals(container, "mkv", StringComparison.OrdinalIgnoreCase))
-            {
-                contentFeatures = "DLNA.ORG_PN=MATROSKA";
-            }
-            else if (string.Equals(container, "mp4", StringComparison.OrdinalIgnoreCase))
-            {
-                contentFeatures = "DLNA.ORG_PN=AVC_MP4_MP_HD_720p_AAC";
-            }
-            else if (string.Equals(container, "mpeg", StringComparison.OrdinalIgnoreCase))
-            {
-                contentFeatures = "DLNA.ORG_PN=MPEG_PS_PAL";
-            }
-            else if (string.Equals(container, "ts", StringComparison.OrdinalIgnoreCase))
-            {
-                contentFeatures = "DLNA.ORG_PN=MPEG_PS_PAL";
-            }
-            else if (streamInfo.MediaType == DlnaProfileType.Video)
-            {
-                // Default to AVI for video
-                contentFeatures = "DLNA.ORG_PN=AVI";
-            }
-            else
+
+            if (streamInfo.MediaType == DlnaProfileType.Video)
             {
-                // Default to MP3 for audio
-                contentFeatures = "DLNA.ORG_PN=MP3";
+                return new ContentFeatureBuilder(profile)
+                    .BuildVideoHeader(streamInfo.Container,
+                    streamInfo.VideoCodec,
+                    streamInfo.AudioCodec,
+                    streamInfo.TargetWidth,
+                    streamInfo.TargetHeight,
+                    streamInfo.TotalOutputBitrate,
+                    streamInfo.TargetTimestamp,
+                    streamInfo.IsDirectStream,
+                    streamInfo.RunTimeTicks,
+                    streamInfo.TranscodeSeekInfo);
             }
 
-            return (contentFeatures + orgOp + orgCi + dlnaflags).Trim(';');
+            return null;
         }
 
         private PlaylistItem GetPlaylistItem(BaseItem item, List<MediaSourceInfo> mediaSources, DeviceProfile profile, string deviceId)
@@ -477,7 +449,9 @@ namespace MediaBrowser.Dlna.PlayTo
                         MediaSources = mediaSources,
                         Profile = profile,
                         DeviceId = deviceId
-                    })
+                    }),
+
+                    Profile = profile
                 };
             }
 
@@ -493,7 +467,9 @@ namespace MediaBrowser.Dlna.PlayTo
                         MediaSources = mediaSources,
                         Profile = profile,
                         DeviceId = deviceId
-                    })
+                    }),
+
+                    Profile = profile
                 };
             }
 

+ 0 - 4
MediaBrowser.Dlna/PlayTo/PlayToManager.cs

@@ -104,10 +104,6 @@ namespace MediaBrowser.Dlna.PlayTo
             }
         }
 
-        public void Stop()
-        {
-        }
-
         /// <summary>
         /// Creates a socket for the interface and listends for data.
         /// </summary>

+ 0 - 1
MediaBrowser.Dlna/PlayTo/PlayToServerEntryPoint.cs

@@ -104,7 +104,6 @@ namespace MediaBrowser.Dlna.PlayTo
                 {
                     try
                     {
-                        _manager.Stop();
                         _manager.Dispose();
                     }
                     catch (Exception ex)

+ 2 - 0
MediaBrowser.Dlna/PlayTo/PlaylistItem.cs

@@ -11,5 +11,7 @@ namespace MediaBrowser.Dlna.PlayTo
         public string Didl { get; set; }
 
         public StreamInfo StreamInfo { get; set; }
+
+        public DeviceProfile Profile { get; set; }
     }
 }

+ 3 - 1
MediaBrowser.Dlna/PlayTo/PlaylistItemFactory.cs

@@ -19,7 +19,9 @@ namespace MediaBrowser.Dlna.PlayTo
                 {
                     ItemId = item.Id.ToString("N"),
                     MediaType = DlnaProfileType.Photo,
-                }
+                },
+
+                Profile = profile
             };
 
             var directPlay = profile.DirectPlayProfiles

+ 5 - 8
MediaBrowser.Dlna/Server/ControlHandler.cs

@@ -632,9 +632,7 @@ namespace MediaBrowser.Dlna.Server
 
             var mediaProfile = _profile.GetVideoMediaProfile(streamInfo.Container,
                 streamInfo.AudioCodec,
-                streamInfo.VideoCodec,
-                streamInfo.TargetAudioStream,
-                streamInfo.TargetVideoStream);
+                streamInfo.VideoCodec);
 
             var formatProfile = mediaProfile == null ? null : mediaProfile.OrgPn;
 
@@ -646,7 +644,7 @@ namespace MediaBrowser.Dlna.Server
                         targetWidth,
                         targetHeight,
                         targetBitrate,
-                        TransportStreamTimestamp.VALID);
+                        streamInfo.TargetTimestamp);
 
                 formatProfile = format.HasValue ? format.Value.ToString() : null;
             }
@@ -731,8 +729,7 @@ namespace MediaBrowser.Dlna.Server
             }
 
             var mediaProfile = _profile.GetAudioMediaProfile(streamInfo.Container,
-                streamInfo.AudioCodec,
-                streamInfo.TargetAudioStream);
+                streamInfo.AudioCodec);
 
             var formatProfile = mediaProfile == null ? null : mediaProfile.OrgPn;
 
@@ -780,11 +777,11 @@ namespace MediaBrowser.Dlna.Server
                 {
                     if (item is MusicAlbum)
                     {
-                        classType = "object.container.musicAlbum";
+                        classType = "object.container.album.musicAlbum";
                     }
                     if (item is MusicArtist)
                     {
-                        classType = "object.container.musicArtist";
+                        classType = "object.container.person.musicArtist";
                     }
                 }
 

+ 130 - 0
MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs

@@ -0,0 +1,130 @@
+using System;
+
+namespace MediaBrowser.Model.Dlna
+{
+    public class ContentFeatureBuilder
+    {
+        private readonly DeviceProfile _profile;
+
+        public ContentFeatureBuilder(DeviceProfile profile)
+        {
+            _profile = profile;
+        }
+
+        public string BuildAudioHeader(string container, 
+            string audioCodec,
+            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
+            var orgOp = ";DLNA.ORG_OP=" + DlnaMaps.GetOrgOpValue(runtimeTicks.HasValue, isDirectStream, transcodeSeekInfo);
+
+            // 0 = native, 1 = transcoded
+            var orgCi = isDirectStream ? ";DLNA.ORG_CI=0" : ";DLNA.ORG_CI=1";
+
+            var flagValue = DlnaFlags.StreamingTransferMode |
+                            DlnaFlags.BackgroundTransferMode |
+                            DlnaFlags.DlnaV15;
+
+            if (isDirectStream)
+            {
+                //flagValue = flagValue | DlnaFlags.DLNA_ORG_FLAG_BYTE_BASED_SEEK;
+            }
+            else if (runtimeTicks.HasValue)
+            {
+                //flagValue = flagValue | DlnaFlags.DLNA_ORG_FLAG_TIME_BASED_SEEK;
+            }
+
+            var dlnaflags = string.Format(";DLNA.ORG_FLAGS={0}000000000000000000000000",
+             Enum.Format(typeof(DlnaFlags), flagValue, "x"));
+
+            var mediaProfile = _profile.GetAudioMediaProfile(container, audioCodec);
+
+            var orgPn = mediaProfile == null ? null : mediaProfile.OrgPn;
+
+            if (string.IsNullOrEmpty(orgPn))
+            {
+                orgPn = GetAudioOrgPnValue(container, audioBitrate, audioSampleRate, audioChannels);
+            }
+
+            var contentFeatures = string.IsNullOrEmpty(orgPn) ? string.Empty : "DLNA.ORG_PN=" + orgPn;
+
+            return (contentFeatures + orgOp + orgCi + dlnaflags).Trim(';');
+        }
+
+        public string BuildVideoHeader(string container, 
+            string videoCodec, 
+            string audioCodec, 
+            int? width, 
+            int? height, 
+            int? bitrate, 
+            TransportStreamTimestamp timestamp, 
+            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
+            var orgOp = ";DLNA.ORG_OP=" + DlnaMaps.GetOrgOpValue(runtimeTicks.HasValue, isDirectStream, transcodeSeekInfo);
+
+            // 0 = native, 1 = transcoded
+            var orgCi = isDirectStream ? ";DLNA.ORG_CI=0" : ";DLNA.ORG_CI=1";
+
+            var flagValue = DlnaFlags.StreamingTransferMode |
+                            DlnaFlags.BackgroundTransferMode |
+                            DlnaFlags.DlnaV15;
+
+            if (isDirectStream)
+            {
+                //flagValue = flagValue | DlnaFlags.DLNA_ORG_FLAG_BYTE_BASED_SEEK;
+            }
+            else if (runtimeTicks.HasValue)
+            {
+                //flagValue = flagValue | DlnaFlags.DLNA_ORG_FLAG_TIME_BASED_SEEK;
+            }
+
+            var dlnaflags = string.Format(";DLNA.ORG_FLAGS={0}000000000000000000000000",
+             Enum.Format(typeof(DlnaFlags), flagValue, "x"));
+
+            var mediaProfile = _profile.GetVideoMediaProfile(container, audioCodec, videoCodec);
+            var orgPn = mediaProfile == null ? null : mediaProfile.OrgPn;
+            
+            if (string.IsNullOrEmpty(orgPn))
+            {
+                orgPn = GetVideoOrgPnValue(container, videoCodec, audioCodec, width, height, bitrate, timestamp);
+            }
+
+            var contentFeatures = string.IsNullOrEmpty(orgPn) ? string.Empty : "DLNA.ORG_PN=" + orgPn;
+
+            return (contentFeatures + orgOp + orgCi + dlnaflags).Trim(';');
+        }
+
+        private string GetAudioOrgPnValue(string container, int? audioBitrate, int? audioSampleRate, int? audioChannels)
+        {
+            var format = new MediaFormatProfileResolver()
+                .ResolveAudioFormat(container,
+                audioBitrate,
+                audioSampleRate,
+                audioChannels);
+
+            return format.HasValue ? format.Value.ToString() : null;
+        }
+
+        private string GetVideoOrgPnValue(string container, string videoCodec, string audioCodec, int? width, int? height, int? bitrate, TransportStreamTimestamp timestamp)
+        {
+            var videoFormat = new MediaFormatProfileResolver()
+                .ResolveVideoFormat(container,
+                    videoCodec,
+                    audioCodec,
+                    width,
+                    height,
+                    bitrate,
+                    timestamp);
+
+            return videoFormat.HasValue ? videoFormat.Value.ToString() : null;
+        }
+    }
+}

+ 2 - 2
MediaBrowser.Model/Dlna/DeviceProfile.cs

@@ -159,7 +159,7 @@ namespace MediaBrowser.Model.Dlna
             });
         }
 
-        public ResponseProfile GetAudioMediaProfile(string container, string audioCodec, MediaStream audioStream)
+        public ResponseProfile GetAudioMediaProfile(string container, string audioCodec)
         {
             container = (container ?? string.Empty).TrimStart('.');
 
@@ -186,7 +186,7 @@ namespace MediaBrowser.Model.Dlna
             });
         }
 
-        public ResponseProfile GetVideoMediaProfile(string container, string audioCodec, string videoCodec, MediaStream audioStream, MediaStream videoStream)
+        public ResponseProfile GetVideoMediaProfile(string container, string audioCodec, string videoCodec)
         {
             container = (container ?? string.Empty).TrimStart('.');
 

+ 2 - 2
MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs

@@ -33,7 +33,7 @@ namespace MediaBrowser.Model.Dlna
                 string.Equals(container, "m2ts", StringComparison.OrdinalIgnoreCase))
             {
 
-                var list = ResolveVideoMPEG2TSFormat(videoCodec, audioCodec, width, height, bitrate, timestampType)
+                var list = ResolveVideoMPEG2TSFormat(videoCodec, audioCodec, width, height, timestampType)
                    .ToList();
 
                 return list.Count > 0 ? list[0] : (MediaFormatProfile?)null;
@@ -54,7 +54,7 @@ namespace MediaBrowser.Model.Dlna
             return null;
         }
 
-        private IEnumerable<MediaFormatProfile> ResolveVideoMPEG2TSFormat(string videoCodec, string audioCodec, int? width, int? height, int? bitrate, TransportStreamTimestamp timestampType)
+        private IEnumerable<MediaFormatProfile> ResolveVideoMPEG2TSFormat(string videoCodec, string audioCodec, int? width, int? height, TransportStreamTimestamp timestampType)
         {
             var suffix = "";
 

+ 66 - 1
MediaBrowser.Model/Dlna/StreamInfo.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Drawing;
+using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
 using System;
 using System.Collections.Generic;
@@ -56,6 +57,8 @@ namespace MediaBrowser.Model.Dlna
 
         public MediaSourceInfo MediaSource { get; set; }
 
+        public TransportStreamTimestamp TargetTimestamp { get; set; }
+
         public string MediaSourceId
         {
             get
@@ -252,6 +255,68 @@ namespace MediaBrowser.Model.Dlna
                     : stream == null ? null : stream.Channels;
             }
         }
+
+        public int? TotalOutputBitrate
+        {
+            get
+            {
+                return (TargetAudioBitrate ?? 0) + (VideoBitrate ?? 0);
+            }
+        }
+
+        public int? TargetWidth
+        {
+            get
+            {
+                var videoStream = TargetVideoStream;
+
+                if (videoStream != null && videoStream.Width.HasValue && videoStream.Height.HasValue)
+                {
+                    var size = new ImageSize
+                    {
+                        Width = videoStream.Width.Value,
+                        Height = videoStream.Height.Value
+                    };
+
+                    var newSize = DrawingUtils.Resize(size,
+                        null,
+                        null,
+                        MaxWidth,
+                        MaxHeight);
+
+                    return Convert.ToInt32(newSize.Width);
+                }
+
+                return MaxWidth;
+            }
+        }
+
+        public int? TargetHeight
+        {
+            get
+            {
+                var videoStream = TargetVideoStream;
+
+                if (videoStream != null && videoStream.Width.HasValue && videoStream.Height.HasValue)
+                {
+                    var size = new ImageSize
+                    {
+                        Width = videoStream.Width.Value,
+                        Height = videoStream.Height.Value
+                    };
+
+                    var newSize = DrawingUtils.Resize(size,
+                        null,
+                        null,
+                        MaxWidth,
+                        MaxHeight);
+
+                    return Convert.ToInt32(newSize.Height);
+                }
+
+                return MaxHeight;
+            }
+        }
     }
 
     /// <summary>

+ 1 - 0
MediaBrowser.Model/MediaBrowser.Model.csproj

@@ -68,6 +68,7 @@
     <Compile Include="Configuration\ServerConfiguration.cs" />
     <Compile Include="Dlna\CodecProfile.cs" />
     <Compile Include="Dlna\ContainerProfile.cs" />
+    <Compile Include="Dlna\ContentFeatureBuilder.cs" />
     <Compile Include="Dlna\DeviceIdentification.cs" />
     <Compile Include="Dlna\DeviceProfile.cs" />
     <Compile Include="Dlna\DeviceProfileInfo.cs" />