浏览代码

support more dlna resource properties

Luke Pulverenti 11 年之前
父节点
当前提交
5170042eb5

+ 2 - 2
MediaBrowser.Api/Playback/BaseStreamingService.cs

@@ -380,7 +380,7 @@ namespace MediaBrowser.Api.Playback
 
                 if (isVc1)
                 {
-                    profileScore ++;
+                    profileScore++;
                     // Max of 2
                     profileScore = Math.Min(profileScore, 2);
                 }
@@ -445,7 +445,7 @@ namespace MediaBrowser.Api.Playback
             {
                 if (state.AudioStream != null && state.AudioStream.Channels.HasValue && state.AudioStream.Channels.Value > 5)
                 {
-                    volParam = ",volume=2.000000";
+                    volParam = ",volume=" + ServerConfigurationManager.Configuration.DownMixAudioBoost.ToString(UsCulture);
                 }
             }
 

+ 9 - 1
MediaBrowser.Dlna/DlnaManager.cs

@@ -516,7 +516,15 @@ namespace MediaBrowser.Dlna
 
             var serverAddress = device.Descriptor.ToString().Substring(0, device.Descriptor.ToString().IndexOf("/dlna", StringComparison.OrdinalIgnoreCase));
 
-            return new ControlHandler(_logger, _userManager, _libraryManager, profile, serverAddress, _dtoService, _imageProcessor, _userDataManager)
+            return new ControlHandler(
+                _logger, 
+                _userManager, 
+                _libraryManager, 
+                profile, 
+                serverAddress, 
+                _dtoService, 
+                _imageProcessor, 
+                _userDataManager)
                 .ProcessControlRequest(request);
         }
 

+ 33 - 31
MediaBrowser.Dlna/Server/ControlHandler.cs

@@ -89,6 +89,8 @@ namespace MediaBrowser.Dlna.Server
                 sparams.Add(e.LocalName, e.InnerText.Trim());
             }
 
+            var deviceId = "fgd";
+
             var env = new XmlDocument();
             env.AppendChild(env.CreateXmlDeclaration("1.0", "utf-8", "yes"));
             var envelope = env.CreateElement("SOAP-ENV", "Envelope", NS_SOAPENV);
@@ -116,7 +118,7 @@ namespace MediaBrowser.Dlna.Server
                     result = HandleGetSystemUpdateID();
                     break;
                 case "Browse":
-                    result = HandleBrowse(sparams, user);
+                    result = HandleBrowse(sparams, user, deviceId);
                     break;
                 case "X_GetFeatureList":
                     result = HandleXGetFeatureList();
@@ -235,7 +237,7 @@ namespace MediaBrowser.Dlna.Server
             return builder.ToString();
         }
 
-        private IEnumerable<KeyValuePair<string, string>> HandleBrowse(Headers sparams, User user)
+        private IEnumerable<KeyValuePair<string, string>> HandleBrowse(Headers sparams, User user, string deviceId)
         {
             var id = sparams["ObjectID"];
             var flag = sparams["BrowseFlag"];
@@ -298,7 +300,7 @@ namespace MediaBrowser.Dlna.Server
                     }
                     else
                     {
-                        Browse_AddItem(result, i, user);
+                        Browse_AddItem(result, i, user, deviceId);
                     }
                 }
             }
@@ -366,7 +368,7 @@ namespace MediaBrowser.Dlna.Server
             }
         }
 
-        private void Browse_AddItem(XmlDocument result, BaseItem item, User user)
+        private void Browse_AddItem(XmlDocument result, BaseItem item, User user, string deviceId)
         {
             var element = result.CreateElement(string.Empty, "item", NS_DIDL);
             element.SetAttribute("restricted", "1");
@@ -389,13 +391,13 @@ namespace MediaBrowser.Dlna.Server
             var audio = item as Audio;
             if (audio != null)
             {
-                AddAudioResource(element, audio);
+                AddAudioResource(element, audio, deviceId);
             }
 
             var video = item as Video;
             if (video != null)
             {
-                AddVideoResource(element, video);
+                AddVideoResource(element, video, deviceId);
             }
 
             AddCover(item, element);
@@ -403,12 +405,7 @@ namespace MediaBrowser.Dlna.Server
             result.DocumentElement.AppendChild(element);
         }
 
-        private string GetDeviceId()
-        {
-            return "erer";
-        }
-
-        private void AddVideoResource(XmlElement container, Video video)
+        private void AddVideoResource(XmlElement container, Video video, string deviceId)
         {
             var res = container.OwnerDocument.CreateElement(string.Empty, "res", NS_DIDL);
 
@@ -421,7 +418,7 @@ namespace MediaBrowser.Dlna.Server
                 ItemId = video.Id.ToString("N"),
                 MediaSources = sources,
                 Profile = _profile,
-                DeviceId = GetDeviceId(),
+                DeviceId = deviceId,
                 MaxBitrate = maxBitrateSetting
             });
 
@@ -435,17 +432,21 @@ namespace MediaBrowser.Dlna.Server
                 res.SetAttribute("duration", TimeSpan.FromTicks(mediaSource.RunTimeTicks.Value).ToString("c", _usCulture));
             }
 
-            if (streamInfo.IsDirectStream && mediaSource.Size.HasValue)
+            if (streamInfo.IsDirectStream || streamInfo.EstimateContentLength)
             {
-                res.SetAttribute("size", mediaSource.Size.Value.ToString(_usCulture));
+                var size = streamInfo.TargetSize;
+
+                if (size.HasValue)
+                {
+                    res.SetAttribute("size", size.Value.ToString(_usCulture));
+                }
             }
 
             var videoStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video && !string.Equals(i.Codec, "mjpeg", StringComparison.OrdinalIgnoreCase));
-            var audioStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
 
-            var targetAudioBitrate = streamInfo.AudioBitrate ?? (audioStream == null ? null : audioStream.BitRate);
-            var targetSampleRate = audioStream == null ? null : audioStream.SampleRate;
-            var targetChannels = streamInfo.MaxAudioChannels ?? (audioStream == null ? null : audioStream.Channels);
+            var targetAudioBitrate = streamInfo.TargetAudioBitrate;
+            var targetSampleRate = streamInfo.TargetAudioSampleRate;
+            var targetChannels = streamInfo.TargetAudioChannels;
 
             var targetWidth = streamInfo.MaxWidth ?? (videoStream == null ? null : videoStream.Width);
             var targetHeight = streamInfo.MaxHeight ?? (videoStream == null ? null : videoStream.Height);
@@ -454,9 +455,7 @@ namespace MediaBrowser.Dlna.Server
                 ? (videoStream == null ? null : videoStream.Codec)
                 : streamInfo.VideoCodec;
 
-            var targetAudioCodec = streamInfo.IsDirectStream
-             ? (audioStream == null ? null : audioStream.Codec)
-             : streamInfo.AudioCodec;
+            var targetAudioCodec = streamInfo.TargetAudioCodec;
 
             var targetBitrate = maxBitrateSetting ?? mediaSource.Bitrate;
 
@@ -506,7 +505,7 @@ namespace MediaBrowser.Dlna.Server
             container.AppendChild(res);
         }
 
-        private void AddAudioResource(XmlElement container, Audio audio)
+        private void AddAudioResource(XmlElement container, Audio audio, string deviceId)
         {
             var res = container.OwnerDocument.CreateElement(string.Empty, "res", NS_DIDL);
 
@@ -517,7 +516,7 @@ namespace MediaBrowser.Dlna.Server
                 ItemId = audio.Id.ToString("N"),
                 MediaSources = sources,
                 Profile = _profile,
-                DeviceId = GetDeviceId()
+                DeviceId = deviceId
             });
 
             var url = streamInfo.ToDlnaUrl(_serverAddress);
@@ -530,16 +529,19 @@ namespace MediaBrowser.Dlna.Server
                 res.SetAttribute("duration", TimeSpan.FromTicks(mediaSource.RunTimeTicks.Value).ToString("c", _usCulture));
             }
 
-            if (streamInfo.IsDirectStream && mediaSource.Size.HasValue)
+            if (streamInfo.IsDirectStream || streamInfo.EstimateContentLength)
             {
-                res.SetAttribute("size", mediaSource.Size.Value.ToString(_usCulture));
-            }
+                var size = streamInfo.TargetSize;
 
-            var audioStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
+                if (size.HasValue)
+                {
+                    res.SetAttribute("size", size.Value.ToString(_usCulture));
+                }
+            }
 
-            var targetAudioBitrate = streamInfo.AudioBitrate ?? (audioStream == null ? null : audioStream.BitRate);
-            var targetSampleRate = audioStream == null ? null : audioStream.SampleRate;
-            var targetChannels = streamInfo.MaxAudioChannels ?? (audioStream == null ? null : audioStream.Channels);
+            var targetAudioBitrate = streamInfo.TargetAudioBitrate;
+            var targetSampleRate = streamInfo.TargetAudioSampleRate;
+            var targetChannels = streamInfo.TargetAudioChannels;
 
             if (targetChannels.HasValue)
             {

+ 1 - 1
MediaBrowser.Dlna/Server/DescriptionXmlBuilder.cs

@@ -57,7 +57,7 @@ namespace MediaBrowser.Dlna.Server
 
         private void AppendDeviceProperties(StringBuilder builder)
         {
-            builder.Append("<UDN>" + SecurityElement.Escape(_serverUdn) + "</UDN>");
+            builder.Append("<UDN>uuid:" + SecurityElement.Escape(_serverUdn) + "</UDN>");
             builder.Append("<dlna:X_DLNACAP>" + SecurityElement.Escape(_profile.XDlnaCap ?? string.Empty) + "</dlna:X_DLNACAP>");
 
             if (!string.IsNullOrWhiteSpace(_profile.XDlnaDoc))

+ 2 - 2
MediaBrowser.Dlna/Server/DlnaServerEntryPoint.cs

@@ -1,11 +1,11 @@
-using System.Linq;
-using MediaBrowser.Common;
+using MediaBrowser.Common;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Plugins;
 using MediaBrowser.Model.Logging;
 using System;
+using System.Linq;
 using System.Net;
 
 namespace MediaBrowser.Dlna.Server

+ 3 - 0
MediaBrowser.Model/Configuration/ServerConfiguration.cs

@@ -222,6 +222,8 @@ namespace MediaBrowser.Model.Configuration
         
         public DlnaOptions DlnaOptions { get; set; }
 
+        public double DownMixAudioBoost { get; set; }
+
         /// <summary>
         /// Initializes a new instance of the <see cref="ServerConfiguration" /> class.
         /// </summary>
@@ -242,6 +244,7 @@ namespace MediaBrowser.Model.Configuration
             EnablePeoplePrefixSubFolders = true;
 
             EnableUPnP = true;
+            DownMixAudioBoost = 2;
 
             MinResumePct = 5;
             MaxResumePct = 90;

+ 69 - 57
MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs

@@ -35,17 +35,23 @@ namespace MediaBrowser.Model.Dlna
 
         private MediaFormatProfile ResolveVideoMPEG2TSFormat(string videoCodec, string audioCodec, int? width, int? height, int? bitrate, TransportStreamTimestamp timestampType)
         {
-            //  String suffix = "";
-            //  if (isNoTimestamp(timestampType))
-            //    suffix = "_ISO";
-            //  else if (timestampType == TransportStreamTimestamp.VALID) {
-            //    suffix = "_T";
-            //  }
+            var suffix = "";
 
-            //  String resolution = "S";
-            //  if ((width.intValue() > 720) || (height.intValue() > 576)) {
-            //    resolution = "H";
-            //  }
+            switch (timestampType)
+            {
+                case TransportStreamTimestamp.NONE:
+                    suffix = "_ISO";
+                    break;
+                case TransportStreamTimestamp.VALID:
+                    suffix = "_T";
+                    break;
+            }
+
+            String resolution = "S";
+            if ((width.HasValue && width.Value > 720) || (height.HasValue && height.Value > 576))
+            {
+                resolution = "H";
+            }
 
             //  if (videoCodec == VideoCodec.MPEG2)
             //  {
@@ -55,54 +61,60 @@ namespace MediaBrowser.Model.Dlna
             //      profiles.add(MediaFormatProfile.MPEG_TS_JP_T);
             //    }
             //    return profiles;
-            //  }if (videoCodec == VideoCodec.H264)
-            //  {
-            //    if (audioCodec == AudioCodec.LPCM)
-            //      return Collections.singletonList(MediaFormatProfile.AVC_TS_HD_50_LPCM_T);
-            //    if (audioCodec == AudioCodec.DTS) {
-            //      if (isNoTimestamp(timestampType)) {
-            //        return Collections.singletonList(MediaFormatProfile.AVC_TS_HD_DTS_ISO);
-            //      }
-            //      return Collections.singletonList(MediaFormatProfile.AVC_TS_HD_DTS_T);
-            //    }
-            //    if (audioCodec == AudioCodec.MP2) {
-            //      if (isNoTimestamp(timestampType)) {
-            //        return Collections.singletonList(MediaFormatProfile.valueOf(String.format("AVC_TS_HP_%sD_MPEG1_L2_ISO", cast(Object[])[ resolution ])));
-            //      }
-            //      return Collections.singletonList(MediaFormatProfile.valueOf(String.format("AVC_TS_HP_%sD_MPEG1_L2_T", cast(Object[])[ resolution ])));
-            //    }
-
-            //    if (audioCodec == AudioCodec.AAC)
-            //      return Collections.singletonList(MediaFormatProfile.valueOf(String.format("AVC_TS_MP_%sD_AAC_MULT5%s", cast(Object[])[ resolution, suffix ])));
-            //    if (audioCodec == AudioCodec.MP3)
-            //      return Collections.singletonList(MediaFormatProfile.valueOf(String.format("AVC_TS_MP_%sD_MPEG1_L3%s", cast(Object[])[ resolution, suffix ])));
-            //    if ((audioCodec is null) || (audioCodec == AudioCodec.AC3)) {
-            //      return Collections.singletonList(MediaFormatProfile.valueOf(String.format("AVC_TS_MP_%sD_AC3%s", cast(Object[])[ resolution, suffix ])));
-            //    }
-            //  }
-            //  else if (videoCodec == VideoCodec.VC1) {
-            //    if ((audioCodec is null) || (audioCodec == AudioCodec.AC3))
-            //    {
-            //      if ((width.intValue() > 720) || (height.intValue() > 576)) {
-            //        return Collections.singletonList(MediaFormatProfile.VC1_TS_AP_L2_AC3_ISO);
-            //      }
-            //      return Collections.singletonList(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 (audioCodec == AudioCodec.AAC)
-            //      return Collections.singletonList(MediaFormatProfile.valueOf(String.format("MPEG4_P2_TS_ASP_AAC%s", cast(Object[])[ suffix ])));
-            //    if (audioCodec == AudioCodec.MP3)
-            //      return Collections.singletonList(MediaFormatProfile.valueOf(String.format("MPEG4_P2_TS_ASP_MPEG1_L3%s", cast(Object[])[ suffix ])));
-            //    if (audioCodec == AudioCodec.MP2)
-            //      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 ])));
-            //    }
             //  }
+            if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase))
+            {
+                if (string.Equals(audioCodec, "lpcm", StringComparison.OrdinalIgnoreCase))
+                    return MediaFormatProfile.AVC_TS_HD_50_LPCM_T;
+
+                if (string.Equals(audioCodec, "dts", StringComparison.OrdinalIgnoreCase))
+                {
+                    if (timestampType == TransportStreamTimestamp.NONE)
+                    {
+                        return MediaFormatProfile.AVC_TS_HD_DTS_ISO;
+                    }
+                    return MediaFormatProfile.AVC_TS_HD_DTS_T;
+                }
+                //if (audioCodec == AudioCodec.MP2) {
+                //  if (isNoTimestamp(timestampType)) {
+                //    return Collections.singletonList(MediaFormatProfile.valueOf(String.format("AVC_TS_HP_%sD_MPEG1_L2_ISO", cast(Object[])[ resolution ])));
+                //  }
+                //  return Collections.singletonList(MediaFormatProfile.valueOf(String.format("AVC_TS_HP_%sD_MPEG1_L2_T", cast(Object[])[ resolution ])));
+                //}
+
+                //if (audioCodec == AudioCodec.AAC)
+                //  return Collections.singletonList(MediaFormatProfile.valueOf(String.format("AVC_TS_MP_%sD_AAC_MULT5%s", cast(Object[])[ resolution, suffix ])));
+                //if (audioCodec == AudioCodec.MP3)
+                //  return Collections.singletonList(MediaFormatProfile.valueOf(String.format("AVC_TS_MP_%sD_MPEG1_L3%s", cast(Object[])[ resolution, suffix ])));
+                //if ((audioCodec is null) || (audioCodec == AudioCodec.AC3)) {
+                //  return Collections.singletonList(MediaFormatProfile.valueOf(String.format("AVC_TS_MP_%sD_AC3%s", cast(Object[])[ resolution, suffix ])));
+                //}
+            }
+            else if (string.Equals(videoCodec, "vc1", StringComparison.OrdinalIgnoreCase))
+            {
+                if (string.IsNullOrEmpty(audioCodec) || string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase))
+                {
+                    if ((width.HasValue && width.Value > 720) || (height.HasValue && height.Value > 576))
+                    {
+                        return MediaFormatProfile.VC1_TS_AP_L2_AC3_ISO;
+                    }
+                    return 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 (audioCodec == AudioCodec.AAC)
+                //    return Collections.singletonList(MediaFormatProfile.valueOf(String.format("MPEG4_P2_TS_ASP_AAC%s", cast(Object[])[ suffix ])));
+                //  if (audioCodec == AudioCodec.MP3)
+                //    return Collections.singletonList(MediaFormatProfile.valueOf(String.format("MPEG4_P2_TS_ASP_MPEG1_L3%s", cast(Object[])[ suffix ])));
+                //  if (audioCodec == AudioCodec.MP2)
+                //    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 ])));
+                //  }
+            }
 
             throw new ArgumentException("Mpeg video file does not match any supported DLNA profile");
         }

+ 4 - 2
MediaBrowser.Model/Dlna/StreamBuilder.cs

@@ -81,7 +81,7 @@ namespace MediaBrowser.Model.Dlna
             {
                 ItemId = options.ItemId,
                 MediaType = DlnaProfileType.Audio,
-                MediaSourceId = item.Id,
+                MediaSource = item,
                 RunTimeTicks = item.RunTimeTicks
             };
 
@@ -116,6 +116,7 @@ namespace MediaBrowser.Model.Dlna
             {
                 playlistItem.IsDirectStream = false;
                 playlistItem.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo;
+                playlistItem.EstimateContentLength = transcodingProfile.EstimateContentLength;
                 playlistItem.Container = transcodingProfile.Container;
                 playlistItem.AudioCodec = transcodingProfile.AudioCodec;
 
@@ -152,7 +153,7 @@ namespace MediaBrowser.Model.Dlna
             {
                 ItemId = options.ItemId,
                 MediaType = DlnaProfileType.Video,
-                MediaSourceId = item.Id,
+                MediaSource = item,
                 RunTimeTicks = item.RunTimeTicks
             };
 
@@ -196,6 +197,7 @@ namespace MediaBrowser.Model.Dlna
             {
                 playlistItem.IsDirectStream = false;
                 playlistItem.Container = transcodingProfile.Container;
+                playlistItem.EstimateContentLength = transcodingProfile.EstimateContentLength;
                 playlistItem.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo;
                 playlistItem.AudioCodec = transcodingProfile.AudioCodec.Split(',').FirstOrDefault();
                 playlistItem.VideoCodec = transcodingProfile.VideoCodec;

+ 144 - 2
MediaBrowser.Model/Dlna/StreamInfo.cs

@@ -1,7 +1,9 @@
 using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
 using System;
 using System.Collections.Generic;
 using System.Globalization;
+using System.Linq;
 
 namespace MediaBrowser.Model.Dlna
 {
@@ -12,8 +14,6 @@ namespace MediaBrowser.Model.Dlna
     {
         public string ItemId { get; set; }
 
-        public string MediaSourceId { get; set; }
-
         public bool IsDirectStream { get; set; }
 
         public DlnaProfileType MediaType { get; set; }
@@ -50,6 +50,18 @@ namespace MediaBrowser.Model.Dlna
 
         public TranscodeSeekInfo TranscodeSeekInfo { get; set; }
 
+        public bool EstimateContentLength { get; set; }
+
+        public MediaSourceInfo MediaSource { get; set; }
+
+        public string MediaSourceId
+        {
+            get
+            {
+                return MediaSource == null ? null : MediaSource.Id;
+            }
+        }
+
         public string ToUrl(string baseUrl)
         {
             return ToDlnaUrl(baseUrl);
@@ -102,6 +114,136 @@ namespace MediaBrowser.Model.Dlna
             return string.Format("Params={0}", string.Join(";", list.ToArray()));
         }
 
+        /// <summary>
+        /// Returns the audio stream that will be used
+        /// </summary>
+        public MediaStream TargetAudioStream
+        {
+            get
+            {
+                if (MediaSource != null)
+                {
+                    var audioStreams = MediaSource.MediaStreams.Where(i => i.Type == MediaStreamType.Audio);
+
+                    if (AudioStreamIndex.HasValue)
+                    {
+                        return audioStreams.FirstOrDefault(i => i.Index == AudioStreamIndex.Value);
+                    }
+
+                    return audioStreams.FirstOrDefault();
+                }
+
+                return null;
+            }
+        }
+
+        /// <summary>
+        /// Returns the video stream that will be used
+        /// </summary>
+        public MediaStream TargetVideoStream
+        {
+            get
+            {
+                if (MediaSource != null)
+                {
+                    return MediaSource.MediaStreams
+                        .FirstOrDefault(i => i.Type == MediaStreamType.Video && (i.Codec ?? string.Empty).IndexOf("jpeg", StringComparison.OrdinalIgnoreCase) == -1);
+                }
+
+                return null;
+            }
+        }
+
+        /// <summary>
+        /// Predicts the audio sample rate that will be in the output stream
+        /// </summary>
+        public int? TargetAudioSampleRate
+        {
+            get
+            {
+                var stream = TargetAudioStream;
+                return stream == null ? null : stream.SampleRate;
+            }
+        }
+
+        /// <summary>
+        /// Predicts the audio bitrate that will be in the output stream
+        /// </summary>
+        public int? TargetAudioBitrate
+        {
+            get
+            {
+                var stream = TargetAudioStream;
+                return AudioBitrate.HasValue && !IsDirectStream
+                    ? AudioBitrate
+                    : stream == null ? null : stream.BitRate;
+            }
+        }
+
+        /// <summary>
+        /// Predicts the audio channels that will be in the output stream
+        /// </summary>
+        public int? TargetAudioChannels
+        {
+            get
+            {
+                var stream = TargetAudioStream;
+
+                return MaxAudioChannels.HasValue && !IsDirectStream
+                    ? (stream.Channels.HasValue ? Math.Min(MaxAudioChannels.Value, stream.Channels.Value) : MaxAudioChannels.Value)
+                    : stream == null ? null : stream.Channels;
+            }
+        }
+
+        /// <summary>
+        /// Predicts the audio codec that will be in the output stream
+        /// </summary>
+        public string TargetAudioCodec
+        {
+            get
+            {
+                var stream = TargetAudioStream;
+
+                return IsDirectStream
+                 ? (stream == null ? null : stream.Codec)
+                 : AudioCodec;
+            }
+        }
+
+        /// <summary>
+        /// Predicts the audio channels that will be in the output stream
+        /// </summary>
+        public long? TargetSize
+        {
+            get
+            {
+                if (IsDirectStream)
+                {
+                    return MediaSource.Bitrate;
+                }
+
+                if (RunTimeTicks.HasValue)
+                {
+                    var totalBitrate = 0;
+
+                    if (AudioBitrate.HasValue)
+                    {
+                        totalBitrate += AudioBitrate.Value;
+                    }
+                    if (VideoBitrate.HasValue)
+                    {
+                        totalBitrate += VideoBitrate.Value;
+                    }
+
+                    return Convert.ToInt64(totalBitrate * TimeSpan.FromTicks(RunTimeTicks.Value).TotalSeconds);
+                }
+                var stream = TargetAudioStream;
+
+                return MaxAudioChannels.HasValue && !IsDirectStream
+                    ? (stream.Channels.HasValue ? Math.Min(MaxAudioChannels.Value, stream.Channels.Value) : MaxAudioChannels.Value)
+                    : stream == null ? null : stream.Channels;
+            }
+        }
     }
 
     /// <summary>

+ 1 - 0
MediaBrowser.Server.Implementations/Localization/LocalizationManager.cs

@@ -356,6 +356,7 @@ namespace MediaBrowser.Server.Implementations.Localization
                 new LocalizatonOption{ Name="Greek", Value="el"},
                 new LocalizatonOption{ Name="Hebrew", Value="he"},
                 new LocalizatonOption{ Name="Italian", Value="it"},
+                new LocalizatonOption{ Name="Kazakh", Value="kk"},
                 new LocalizatonOption{ Name="Norwegian Bokmål", Value="nb"},
                 new LocalizatonOption{ Name="Portuguese (Brazil)", Value="pt-BR"},
                 new LocalizatonOption{ Name="Portuguese (Portugal)", Value="pt-PT"},

+ 3 - 1
MediaBrowser.Server.Implementations/Localization/Server/server.json

@@ -515,5 +515,7 @@
 	"ServerUpToDate": "Media Browser Server is up to date",
 	"ErrorConnectingToMediaBrowserRepository": "There was an error connecting to the remote Media Browser repository.",
 	"LabelComponentsUpdated": "The following components have been installed or updated:",
-	"MessagePleaseRestartServerToFinishUpdating": "Please restart the server to finish applying updates."
+	"MessagePleaseRestartServerToFinishUpdating": "Please restart the server to finish applying updates.",
+	"LabelDownMixAudioScale": "Down mix audio boost scale:",
+	"LabelDownMixAudioScaleHelp": "Boost audio when downmixing. Set to 1 to preserve original volume value."
 }