Selaa lähdekoodia

improve direct play of live streams

Luke Pulverenti 10 vuotta sitten
vanhempi
sitoutus
2b7a80cfb5

+ 1 - 1
MediaBrowser.Api/Playback/MediaInfoService.cs

@@ -223,7 +223,7 @@ namespace MediaBrowser.Api.Playback
             int? subtitleStreamIndex,
             string playSessionId)
         {
-            var streamBuilder = new StreamBuilder();
+            var streamBuilder = new StreamBuilder(Logger);
 
             var options = new VideoOptions
             {

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

@@ -211,7 +211,6 @@
     <Compile Include="MediaEncoding\IEncodingManager.cs" />
     <Compile Include="MediaEncoding\ImageEncodingOptions.cs" />
     <Compile Include="MediaEncoding\IMediaEncoder.cs" />
-    <Compile Include="MediaEncoding\InternalMediaInfoResult.cs" />
     <Compile Include="MediaEncoding\ISubtitleEncoder.cs" />
     <Compile Include="MediaEncoding\MediaStreamSelector.cs" />
     <Compile Include="Net\AuthenticatedAttribute.cs" />

+ 4 - 2
MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs

@@ -63,16 +63,18 @@ namespace MediaBrowser.Controller.MediaEncoding
             string filenamePrefix, 
             int? maxWidth,
             CancellationToken cancellationToken);
-        
+
         /// <summary>
         /// Gets the media info.
         /// </summary>
         /// <param name="inputFiles">The input files.</param>
+        /// <param name="primaryPath">The primary path.</param>
         /// <param name="protocol">The protocol.</param>
         /// <param name="isAudio">if set to <c>true</c> [is audio].</param>
+        /// <param name="extractChapters">if set to <c>true</c> [extract chapters].</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
-        Task<InternalMediaInfoResult> GetMediaInfo(string[] inputFiles, MediaProtocol protocol, bool isAudio, CancellationToken cancellationToken);
+        Task<MediaInfo> GetMediaInfo(string[] inputFiles, string primaryPath, MediaProtocol protocol, bool isAudio, bool extractChapters, CancellationToken cancellationToken);
 
         /// <summary>
         /// Gets the probe size argument.

+ 1 - 289
MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs

@@ -1,9 +1,7 @@
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.IO;
+using MediaBrowser.Model.IO;
 using MediaBrowser.Model.MediaInfo;
 using System;
 using System.Collections.Generic;
-using System.Globalization;
 using System.IO;
 using System.Linq;
 
@@ -46,291 +44,5 @@ namespace MediaBrowser.Controller.MediaEncoding
                 .Where(f => !string.IsNullOrEmpty(f))
                 .ToList();
         }
-
-        public static MediaInfo GetMediaInfo(InternalMediaInfoResult data)
-        {
-            var internalStreams = data.streams ?? new MediaStreamInfo[] { };
-
-            var info = new MediaInfo
-            {
-                MediaStreams = internalStreams.Select(s => GetMediaStream(s, data.format))
-                    .Where(i => i != null)
-                    .ToList()
-            };
-
-            if (data.format != null)
-            {
-                info.Format = data.format.format_name;
-
-                if (!string.IsNullOrEmpty(data.format.bit_rate))
-                {
-                    info.TotalBitrate = int.Parse(data.format.bit_rate, UsCulture);
-                }
-            }
-
-            return info;
-        }
-
-        private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
-
-        /// <summary>
-        /// Converts ffprobe stream info to our MediaStream class
-        /// </summary>
-        /// <param name="streamInfo">The stream info.</param>
-        /// <param name="formatInfo">The format info.</param>
-        /// <returns>MediaStream.</returns>
-        private static MediaStream GetMediaStream(MediaStreamInfo streamInfo, MediaFormatInfo formatInfo)
-        {
-            var stream = new MediaStream
-            {
-                Codec = streamInfo.codec_name,
-                Profile = streamInfo.profile,
-                Level = streamInfo.level,
-                Index = streamInfo.index,
-                PixelFormat = streamInfo.pix_fmt
-            };
-
-            if (streamInfo.tags != null)
-            {
-                stream.Language = GetDictionaryValue(streamInfo.tags, "language");
-            }
-
-            if (string.Equals(streamInfo.codec_type, "audio", StringComparison.OrdinalIgnoreCase))
-            {
-                stream.Type = MediaStreamType.Audio;
-
-                stream.Channels = streamInfo.channels;
-
-                if (!string.IsNullOrEmpty(streamInfo.sample_rate))
-                {
-                    stream.SampleRate = int.Parse(streamInfo.sample_rate, UsCulture);
-                }
-
-                stream.ChannelLayout = ParseChannelLayout(streamInfo.channel_layout);
-            }
-            else if (string.Equals(streamInfo.codec_type, "subtitle", StringComparison.OrdinalIgnoreCase))
-            {
-                stream.Type = MediaStreamType.Subtitle;
-            }
-            else if (string.Equals(streamInfo.codec_type, "video", StringComparison.OrdinalIgnoreCase))
-            {
-                stream.Type = (streamInfo.codec_name ?? string.Empty).IndexOf("mjpeg", StringComparison.OrdinalIgnoreCase) != -1
-                    ? MediaStreamType.EmbeddedImage
-                    : MediaStreamType.Video;
-
-                stream.Width = streamInfo.width;
-                stream.Height = streamInfo.height;
-                stream.AspectRatio = GetAspectRatio(streamInfo);
-
-                stream.AverageFrameRate = GetFrameRate(streamInfo.avg_frame_rate);
-                stream.RealFrameRate = GetFrameRate(streamInfo.r_frame_rate);
-
-                stream.BitDepth = GetBitDepth(stream.PixelFormat);
-
-                //stream.IsAnamorphic = string.Equals(streamInfo.sample_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase) ||
-                //    string.Equals(stream.AspectRatio, "2.35:1", StringComparison.OrdinalIgnoreCase) ||
-                //    string.Equals(stream.AspectRatio, "2.40:1", StringComparison.OrdinalIgnoreCase);
-
-                stream.IsAnamorphic = string.Equals(streamInfo.sample_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase);
-            }
-            else
-            {
-                return null;
-            }
-
-            // Get stream bitrate
-            var bitrate = 0;
-
-            if (!string.IsNullOrEmpty(streamInfo.bit_rate))
-            {
-                bitrate = int.Parse(streamInfo.bit_rate, UsCulture);
-            }
-            else if (formatInfo != null && !string.IsNullOrEmpty(formatInfo.bit_rate) && stream.Type == MediaStreamType.Video)
-            {
-                // If the stream info doesn't have a bitrate get the value from the media format info
-                bitrate = int.Parse(formatInfo.bit_rate, UsCulture);
-            }
-
-            if (bitrate > 0)
-            {
-                stream.BitRate = bitrate;
-            }
-
-            if (streamInfo.disposition != null)
-            {
-                var isDefault = GetDictionaryValue(streamInfo.disposition, "default");
-                var isForced = GetDictionaryValue(streamInfo.disposition, "forced");
-
-                stream.IsDefault = string.Equals(isDefault, "1", StringComparison.OrdinalIgnoreCase);
-
-                stream.IsForced = string.Equals(isForced, "1", StringComparison.OrdinalIgnoreCase);
-            }
-
-            return stream;
-        }
-
-        private static int? GetBitDepth(string pixelFormat)
-        {
-            var eightBit = new List<string>
-            {
-                "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;
-        }
-
-        /// <summary>
-        /// Gets a string from an FFProbeResult tags dictionary
-        /// </summary>
-        /// <param name="tags">The tags.</param>
-        /// <param name="key">The key.</param>
-        /// <returns>System.String.</returns>
-        private static string GetDictionaryValue(Dictionary<string, string> tags, string key)
-        {
-            if (tags == null)
-            {
-                return null;
-            }
-
-            string val;
-
-            tags.TryGetValue(key, out val);
-            return val;
-        }
-
-        private static string ParseChannelLayout(string input)
-        {
-            if (string.IsNullOrEmpty(input))
-            {
-                return input;
-            }
-
-            return input.Split('(').FirstOrDefault();
-        }
-
-        private static string GetAspectRatio(MediaStreamInfo info)
-        {
-            var original = info.display_aspect_ratio;
-
-            int height;
-            int width;
-
-            var parts = (original ?? string.Empty).Split(':');
-            if (!(parts.Length == 2 &&
-                int.TryParse(parts[0], NumberStyles.Any, UsCulture, out width) &&
-                int.TryParse(parts[1], NumberStyles.Any, UsCulture, out height) &&
-                width > 0 &&
-                height > 0))
-            {
-                width = info.width;
-                height = info.height;
-            }
-
-            if (width > 0 && height > 0)
-            {
-                double ratio = width;
-                ratio /= height;
-
-                if (IsClose(ratio, 1.777777778, .03))
-                {
-                    return "16:9";
-                }
-
-                if (IsClose(ratio, 1.3333333333, .05))
-                {
-                    return "4:3";
-                }
-
-                if (IsClose(ratio, 1.41))
-                {
-                    return "1.41:1";
-                }
-
-                if (IsClose(ratio, 1.5))
-                {
-                    return "1.5:1";
-                }
-
-                if (IsClose(ratio, 1.6))
-                {
-                    return "1.6:1";
-                }
-
-                if (IsClose(ratio, 1.66666666667))
-                {
-                    return "5:3";
-                }
-
-                if (IsClose(ratio, 1.85, .02))
-                {
-                    return "1.85:1";
-                }
-
-                if (IsClose(ratio, 2.35, .025))
-                {
-                    return "2.35:1";
-                }
-
-                if (IsClose(ratio, 2.4, .025))
-                {
-                    return "2.40:1";
-                }
-            }
-
-            return original;
-        }
-
-        private static bool IsClose(double d1, double d2, double variance = .005)
-        {
-            return Math.Abs(d1 - d2) <= variance;
-        }
-
-        /// <summary>
-        /// Gets a frame rate from a string value in ffprobe output
-        /// This could be a number or in the format of 2997/125.
-        /// </summary>
-        /// <param name="value">The value.</param>
-        /// <returns>System.Nullable{System.Single}.</returns>
-        private static float? GetFrameRate(string value)
-        {
-            if (!string.IsNullOrEmpty(value))
-            {
-                var parts = value.Split('/');
-
-                float result;
-
-                if (parts.Length == 2)
-                {
-                    result = float.Parse(parts[0], UsCulture) / float.Parse(parts[1], UsCulture);
-                }
-                else
-                {
-                    result = float.Parse(parts[0], UsCulture);
-                }
-
-                return float.IsNaN(result) ? (float?)null : result;
-            }
-
-            return null;
-        }
-
     }
 }

+ 3 - 2
MediaBrowser.Dlna/Didl/DidlBuilder.cs

@@ -12,6 +12,7 @@ using MediaBrowser.Dlna.ContentDirectory;
 using MediaBrowser.Model.Dlna;
 using MediaBrowser.Model.Drawing;
 using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Net;
 using System;
 using System.Globalization;
@@ -126,7 +127,7 @@ namespace MediaBrowser.Dlna.Didl
             {
                 var sources = _mediaSourceManager.GetStaticMediaSources(video, true, _user).ToList();
 
-                streamInfo = new StreamBuilder().BuildVideoItem(new VideoOptions
+                streamInfo = new StreamBuilder(new NullLogger()).BuildVideoItem(new VideoOptions
                 {
                     ItemId = GetClientId(video),
                     MediaSources = sources,
@@ -353,7 +354,7 @@ namespace MediaBrowser.Dlna.Didl
             {
                 var sources = _mediaSourceManager.GetStaticMediaSources(audio, true, _user).ToList();
 
-                streamInfo = new StreamBuilder().BuildAudioItem(new AudioOptions
+                streamInfo = new StreamBuilder(new NullLogger()).BuildAudioItem(new AudioOptions
                {
                    ItemId = GetClientId(audio),
                    MediaSources = sources,

+ 2 - 2
MediaBrowser.Dlna/PlayTo/PlayToController.cs

@@ -542,7 +542,7 @@ namespace MediaBrowser.Dlna.PlayTo
             {
                 return new PlaylistItem
                 {
-                    StreamInfo = new StreamBuilder().BuildVideoItem(new VideoOptions
+                    StreamInfo = new StreamBuilder(_logger).BuildVideoItem(new VideoOptions
                     {
                         ItemId = item.Id.ToString("N"),
                         MediaSources = mediaSources,
@@ -562,7 +562,7 @@ namespace MediaBrowser.Dlna.PlayTo
             {
                 return new PlaylistItem
                 {
-                    StreamInfo = new StreamBuilder().BuildAudioItem(new AudioOptions
+                    StreamInfo = new StreamBuilder(_logger).BuildAudioItem(new AudioOptions
                     {
                         ItemId = item.Id.ToString("N"),
                         MediaSources = mediaSources,

+ 13 - 6
MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs

@@ -5,6 +5,7 @@ using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Controller.MediaEncoding;
 using MediaBrowser.Controller.Session;
+using MediaBrowser.MediaEncoding.Probing;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Logging;
@@ -103,15 +104,17 @@ namespace MediaBrowser.MediaEncoding.Encoder
         /// Gets the media info.
         /// </summary>
         /// <param name="inputFiles">The input files.</param>
+        /// <param name="primaryPath">The primary path.</param>
         /// <param name="protocol">The protocol.</param>
         /// <param name="isAudio">if set to <c>true</c> [is audio].</param>
+        /// <param name="extractChapters">if set to <c>true</c> [extract chapters].</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
-        public Task<InternalMediaInfoResult> GetMediaInfo(string[] inputFiles, MediaProtocol protocol, bool isAudio,
-            CancellationToken cancellationToken)
+        public Task<Model.Entities.MediaInfo> GetMediaInfo(string[] inputFiles, string primaryPath, MediaProtocol protocol, bool isAudio,
+            bool extractChapters, CancellationToken cancellationToken)
         {
-            return GetMediaInfoInternal(GetInputArgument(inputFiles, protocol), !isAudio,
-                GetProbeSizeArgument(inputFiles, protocol), cancellationToken);
+            return GetMediaInfoInternal(GetInputArgument(inputFiles, protocol), primaryPath, protocol, !isAudio && extractChapters,
+                GetProbeSizeArgument(inputFiles, protocol), isAudio, cancellationToken);
         }
 
         /// <summary>
@@ -141,13 +144,17 @@ namespace MediaBrowser.MediaEncoding.Encoder
         /// Gets the media info internal.
         /// </summary>
         /// <param name="inputPath">The input path.</param>
+        /// <param name="primaryPath">The primary path.</param>
+        /// <param name="protocol">The protocol.</param>
         /// <param name="extractChapters">if set to <c>true</c> [extract chapters].</param>
         /// <param name="probeSizeArgument">The probe size argument.</param>
+        /// <param name="isAudio">if set to <c>true</c> [is audio].</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task{MediaInfoResult}.</returns>
         /// <exception cref="System.ApplicationException"></exception>
-        private async Task<InternalMediaInfoResult> GetMediaInfoInternal(string inputPath, bool extractChapters,
+        private async Task<Model.Entities.MediaInfo> GetMediaInfoInternal(string inputPath, string primaryPath, MediaProtocol protocol, bool extractChapters,
             string probeSizeArgument,
+            bool isAudio,
             CancellationToken cancellationToken)
         {
             var args = extractChapters
@@ -244,7 +251,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
                 }
             }
 
-            return result;
+            return new ProbeResultNormalizer(_logger, FileSystem).GetMediaInfo(result, isAudio, primaryPath, protocol);
         }
 
         /// <summary>

+ 7 - 0
MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj

@@ -68,6 +68,9 @@
     <Compile Include="Encoder\JobLogger.cs" />
     <Compile Include="Encoder\MediaEncoder.cs" />
     <Compile Include="Encoder\VideoEncoder.cs" />
+    <Compile Include="Probing\FFProbeHelpers.cs" />
+    <Compile Include="Probing\InternalMediaInfoResult.cs" />
+    <Compile Include="Probing\ProbeResultNormalizer.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="Subtitles\ISubtitleParser.cs" />
     <Compile Include="Subtitles\ISubtitleWriter.cs" />
@@ -91,6 +94,10 @@
       <Project>{17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2}</Project>
       <Name>MediaBrowser.Controller</Name>
     </ProjectReference>
+    <ProjectReference Include="..\MediaBrowser.MediaInfo\MediaBrowser.MediaInfo.csproj">
+      <Project>{6e4145e4-c6d4-4e4d-94f2-87188db6e239}</Project>
+      <Name>MediaBrowser.MediaInfo</Name>
+    </ProjectReference>
     <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj">
       <Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project>
       <Name>MediaBrowser.Model</Name>

+ 1 - 1
MediaBrowser.Providers/MediaInfo/FFProbeHelpers.cs → MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs

@@ -2,7 +2,7 @@
 using System;
 using System.Collections.Generic;
 
-namespace MediaBrowser.Providers.MediaInfo
+namespace MediaBrowser.MediaEncoding.Probing
 {
     public static class FFProbeHelpers
     {

+ 3 - 3
MediaBrowser.Controller/MediaEncoding/InternalMediaInfoResult.cs → MediaBrowser.MediaEncoding/Probing/InternalMediaInfoResult.cs

@@ -1,6 +1,6 @@
 using System.Collections.Generic;
 
-namespace MediaBrowser.Controller.MediaEncoding
+namespace MediaBrowser.MediaEncoding.Probing
 {
     /// <summary>
     /// Class MediaInfoResult
@@ -89,7 +89,7 @@ namespace MediaBrowser.Controller.MediaEncoding
         /// </summary>
         /// <value>The channel_layout.</value>
         public string channel_layout { get; set; }
-        
+
         /// <summary>
         /// Gets or sets the avg_frame_rate.
         /// </summary>
@@ -317,7 +317,7 @@ namespace MediaBrowser.Controller.MediaEncoding
         /// </summary>
         /// <value>The probe_score.</value>
         public int probe_score { get; set; }
-        
+
         /// <summary>
         /// Gets or sets the tags.
         /// </summary>

+ 882 - 0
MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs

@@ -0,0 +1,882 @@
+using MediaBrowser.Common.IO;
+using MediaBrowser.MediaInfo;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Extensions;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.MediaInfo;
+
+namespace MediaBrowser.MediaEncoding.Probing
+{
+    public class ProbeResultNormalizer
+    {
+        private readonly CultureInfo _usCulture = new CultureInfo("en-US");
+        private readonly ILogger _logger;
+        private readonly IFileSystem _fileSystem;
+
+        public ProbeResultNormalizer(ILogger logger, IFileSystem fileSystem)
+        {
+            _logger = logger;
+            _fileSystem = fileSystem;
+        }
+
+        public Model.Entities.MediaInfo GetMediaInfo(InternalMediaInfoResult data, bool isAudio, string path, MediaProtocol protocol)
+        {
+            var info = new Model.Entities.MediaInfo
+            {
+                Path = path,
+                Protocol = protocol
+            };
+
+            FFProbeHelpers.NormalizeFFProbeResult(data);
+            SetSize(data, info);
+
+            var internalStreams = data.streams ?? new MediaStreamInfo[] { };
+
+            info.MediaStreams = internalStreams.Select(s => GetMediaStream(s, data.format))
+                .Where(i => i != null)
+                .ToList();
+
+            if (data.format != null)
+            {
+                info.Container = data.format.format_name;
+
+                if (!string.IsNullOrEmpty(data.format.bit_rate))
+                {
+                    info.Bitrate = int.Parse(data.format.bit_rate, _usCulture);
+                }
+            }
+
+            if (isAudio)
+            {
+                SetAudioRuntimeTicks(data, info);
+
+                if (data.format != null && data.format.tags != null)
+                {
+                    SetAudioInfoFromTags(info, data.format.tags);
+                }
+            }
+            else
+            {
+                FetchWtvInfo(info, data);
+
+                if (data.Chapters != null)
+                {
+                    info.Chapters = data.Chapters.Select(GetChapterInfo).ToList();
+                }
+
+                ExtractTimestamp(info);
+
+                var videoStream = info.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);
+
+                if (videoStream != null)
+                {
+                    UpdateFromMediaInfo(info, videoStream);
+                }
+            }
+
+            return info;
+        }
+
+        /// <summary>
+        /// Converts ffprobe stream info to our MediaStream class
+        /// </summary>
+        /// <param name="streamInfo">The stream info.</param>
+        /// <param name="formatInfo">The format info.</param>
+        /// <returns>MediaStream.</returns>
+        private MediaStream GetMediaStream(MediaStreamInfo streamInfo, MediaFormatInfo formatInfo)
+        {
+            var stream = new MediaStream
+            {
+                Codec = streamInfo.codec_name,
+                Profile = streamInfo.profile,
+                Level = streamInfo.level,
+                Index = streamInfo.index,
+                PixelFormat = streamInfo.pix_fmt
+            };
+
+            if (streamInfo.tags != null)
+            {
+                stream.Language = GetDictionaryValue(streamInfo.tags, "language");
+            }
+
+            if (string.Equals(streamInfo.codec_type, "audio", StringComparison.OrdinalIgnoreCase))
+            {
+                stream.Type = MediaStreamType.Audio;
+
+                stream.Channels = streamInfo.channels;
+
+                if (!string.IsNullOrEmpty(streamInfo.sample_rate))
+                {
+                    stream.SampleRate = int.Parse(streamInfo.sample_rate, _usCulture);
+                }
+
+                stream.ChannelLayout = ParseChannelLayout(streamInfo.channel_layout);
+            }
+            else if (string.Equals(streamInfo.codec_type, "subtitle", StringComparison.OrdinalIgnoreCase))
+            {
+                stream.Type = MediaStreamType.Subtitle;
+            }
+            else if (string.Equals(streamInfo.codec_type, "video", StringComparison.OrdinalIgnoreCase))
+            {
+                stream.Type = (streamInfo.codec_name ?? string.Empty).IndexOf("mjpeg", StringComparison.OrdinalIgnoreCase) != -1
+                    ? MediaStreamType.EmbeddedImage
+                    : MediaStreamType.Video;
+
+                stream.Width = streamInfo.width;
+                stream.Height = streamInfo.height;
+                stream.AspectRatio = GetAspectRatio(streamInfo);
+
+                stream.AverageFrameRate = GetFrameRate(streamInfo.avg_frame_rate);
+                stream.RealFrameRate = GetFrameRate(streamInfo.r_frame_rate);
+
+                stream.BitDepth = GetBitDepth(stream.PixelFormat);
+
+                //stream.IsAnamorphic = string.Equals(streamInfo.sample_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase) ||
+                //    string.Equals(stream.AspectRatio, "2.35:1", StringComparison.OrdinalIgnoreCase) ||
+                //    string.Equals(stream.AspectRatio, "2.40:1", StringComparison.OrdinalIgnoreCase);
+
+                stream.IsAnamorphic = string.Equals(streamInfo.sample_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase);
+            }
+            else
+            {
+                return null;
+            }
+
+            // Get stream bitrate
+            var bitrate = 0;
+
+            if (!string.IsNullOrEmpty(streamInfo.bit_rate))
+            {
+                bitrate = int.Parse(streamInfo.bit_rate, _usCulture);
+            }
+            else if (formatInfo != null && !string.IsNullOrEmpty(formatInfo.bit_rate) && stream.Type == MediaStreamType.Video)
+            {
+                // If the stream info doesn't have a bitrate get the value from the media format info
+                bitrate = int.Parse(formatInfo.bit_rate, _usCulture);
+            }
+
+            if (bitrate > 0)
+            {
+                stream.BitRate = bitrate;
+            }
+
+            if (streamInfo.disposition != null)
+            {
+                var isDefault = GetDictionaryValue(streamInfo.disposition, "default");
+                var isForced = GetDictionaryValue(streamInfo.disposition, "forced");
+
+                stream.IsDefault = string.Equals(isDefault, "1", StringComparison.OrdinalIgnoreCase);
+
+                stream.IsForced = string.Equals(isForced, "1", StringComparison.OrdinalIgnoreCase);
+            }
+
+            return stream;
+        }
+
+        private int? GetBitDepth(string pixelFormat)
+        {
+            var eightBit = new List<string>
+            {
+                "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;
+        }
+
+        /// <summary>
+        /// Gets a string from an FFProbeResult tags dictionary
+        /// </summary>
+        /// <param name="tags">The tags.</param>
+        /// <param name="key">The key.</param>
+        /// <returns>System.String.</returns>
+        private string GetDictionaryValue(Dictionary<string, string> tags, string key)
+        {
+            if (tags == null)
+            {
+                return null;
+            }
+
+            string val;
+
+            tags.TryGetValue(key, out val);
+            return val;
+        }
+
+        private string ParseChannelLayout(string input)
+        {
+            if (string.IsNullOrEmpty(input))
+            {
+                return input;
+            }
+
+            return input.Split('(').FirstOrDefault();
+        }
+
+        private string GetAspectRatio(MediaStreamInfo info)
+        {
+            var original = info.display_aspect_ratio;
+
+            int height;
+            int width;
+
+            var parts = (original ?? string.Empty).Split(':');
+            if (!(parts.Length == 2 &&
+                int.TryParse(parts[0], NumberStyles.Any, _usCulture, out width) &&
+                int.TryParse(parts[1], NumberStyles.Any, _usCulture, out height) &&
+                width > 0 &&
+                height > 0))
+            {
+                width = info.width;
+                height = info.height;
+            }
+
+            if (width > 0 && height > 0)
+            {
+                double ratio = width;
+                ratio /= height;
+
+                if (IsClose(ratio, 1.777777778, .03))
+                {
+                    return "16:9";
+                }
+
+                if (IsClose(ratio, 1.3333333333, .05))
+                {
+                    return "4:3";
+                }
+
+                if (IsClose(ratio, 1.41))
+                {
+                    return "1.41:1";
+                }
+
+                if (IsClose(ratio, 1.5))
+                {
+                    return "1.5:1";
+                }
+
+                if (IsClose(ratio, 1.6))
+                {
+                    return "1.6:1";
+                }
+
+                if (IsClose(ratio, 1.66666666667))
+                {
+                    return "5:3";
+                }
+
+                if (IsClose(ratio, 1.85, .02))
+                {
+                    return "1.85:1";
+                }
+
+                if (IsClose(ratio, 2.35, .025))
+                {
+                    return "2.35:1";
+                }
+
+                if (IsClose(ratio, 2.4, .025))
+                {
+                    return "2.40:1";
+                }
+            }
+
+            return original;
+        }
+
+        private bool IsClose(double d1, double d2, double variance = .005)
+        {
+            return Math.Abs(d1 - d2) <= variance;
+        }
+
+        /// <summary>
+        /// Gets a frame rate from a string value in ffprobe output
+        /// This could be a number or in the format of 2997/125.
+        /// </summary>
+        /// <param name="value">The value.</param>
+        /// <returns>System.Nullable{System.Single}.</returns>
+        private float? GetFrameRate(string value)
+        {
+            if (!string.IsNullOrEmpty(value))
+            {
+                var parts = value.Split('/');
+
+                float result;
+
+                if (parts.Length == 2)
+                {
+                    result = float.Parse(parts[0], _usCulture) / float.Parse(parts[1], _usCulture);
+                }
+                else
+                {
+                    result = float.Parse(parts[0], _usCulture);
+                }
+
+                return float.IsNaN(result) ? (float?)null : result;
+            }
+
+            return null;
+        }
+
+        private void SetAudioRuntimeTicks(InternalMediaInfoResult result, Model.Entities.MediaInfo data)
+        {
+            if (result.streams != null)
+            {
+                // Get the first audio stream
+                var stream = result.streams.FirstOrDefault(s => string.Equals(s.codec_type, "audio", StringComparison.OrdinalIgnoreCase));
+
+                if (stream != null)
+                {
+                    // Get duration from stream properties
+                    var duration = stream.duration;
+
+                    // If it's not there go into format properties
+                    if (string.IsNullOrEmpty(duration))
+                    {
+                        duration = result.format.duration;
+                    }
+
+                    // If we got something, parse it
+                    if (!string.IsNullOrEmpty(duration))
+                    {
+                        data.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(duration, _usCulture)).Ticks;
+                    }
+                }
+            }
+        }
+
+        private void SetSize(InternalMediaInfoResult data, Model.Entities.MediaInfo info)
+        {
+            if (data.format != null)
+            {
+                if (!string.IsNullOrEmpty(data.format.size))
+                {
+                    info.Size = long.Parse(data.format.size, _usCulture);
+                }
+                else
+                {
+                    info.Size = null;
+                }
+            }
+        }
+
+        private void SetAudioInfoFromTags(Model.Entities.MediaInfo audio, Dictionary<string, string> tags)
+        {
+            var title = FFProbeHelpers.GetDictionaryValue(tags, "title");
+
+            // Only set Name if title was found in the dictionary
+            if (!string.IsNullOrEmpty(title))
+            {
+                audio.Title = title;
+            }
+
+            var composer = FFProbeHelpers.GetDictionaryValue(tags, "composer");
+
+            if (!string.IsNullOrWhiteSpace(composer))
+            {
+                foreach (var person in Split(composer, false))
+                {
+                    audio.People.Add(new BaseItemPerson { Name = person, Type = PersonType.Composer });
+                }
+            }
+
+            audio.Album = FFProbeHelpers.GetDictionaryValue(tags, "album");
+
+            var artists = FFProbeHelpers.GetDictionaryValue(tags, "artists");
+
+            if (!string.IsNullOrWhiteSpace(artists))
+            {
+                audio.Artists = artists.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries)
+                    .Distinct(StringComparer.OrdinalIgnoreCase)
+                    .ToList();
+            }
+            else
+            {
+                var artist = FFProbeHelpers.GetDictionaryValue(tags, "artist");
+                if (string.IsNullOrWhiteSpace(artist))
+                {
+                    audio.Artists.Clear();
+                }
+                else
+                {
+                    audio.Artists = SplitArtists(artist)
+                        .Distinct(StringComparer.OrdinalIgnoreCase)
+                        .ToList();
+                }
+            }
+
+            var albumArtist = FFProbeHelpers.GetDictionaryValue(tags, "albumartist");
+            if (string.IsNullOrWhiteSpace(albumArtist))
+            {
+                albumArtist = FFProbeHelpers.GetDictionaryValue(tags, "album artist");
+            }
+            if (string.IsNullOrWhiteSpace(albumArtist))
+            {
+                albumArtist = FFProbeHelpers.GetDictionaryValue(tags, "album_artist");
+            }
+
+            if (string.IsNullOrWhiteSpace(albumArtist))
+            {
+                audio.AlbumArtists = new List<string>();
+            }
+            else
+            {
+                audio.AlbumArtists = SplitArtists(albumArtist)
+                    .Distinct(StringComparer.OrdinalIgnoreCase)
+                    .ToList();
+
+            }
+
+            // Track number
+            audio.IndexNumber = GetDictionaryDiscValue(tags, "track");
+
+            // Disc number
+            audio.ParentIndexNumber = GetDictionaryDiscValue(tags, "disc");
+
+            audio.ProductionYear = FFProbeHelpers.GetDictionaryNumericValue(tags, "date");
+
+            // Several different forms of retaildate
+            audio.PremiereDate = FFProbeHelpers.GetDictionaryDateTime(tags, "retaildate") ??
+                FFProbeHelpers.GetDictionaryDateTime(tags, "retail date") ??
+                FFProbeHelpers.GetDictionaryDateTime(tags, "retail_date") ??
+                FFProbeHelpers.GetDictionaryDateTime(tags, "date");
+
+            // If we don't have a ProductionYear try and get it from PremiereDate
+            if (audio.PremiereDate.HasValue && !audio.ProductionYear.HasValue)
+            {
+                audio.ProductionYear = audio.PremiereDate.Value.ToLocalTime().Year;
+            }
+
+            FetchGenres(audio, tags);
+
+            // There's several values in tags may or may not be present
+            FetchStudios(audio, tags, "organization");
+            FetchStudios(audio, tags, "ensemble");
+            FetchStudios(audio, tags, "publisher");
+
+            // These support mulitple values, but for now we only store the first.
+            audio.SetProviderId(MetadataProviders.MusicBrainzAlbumArtist, GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Album Artist Id")));
+            audio.SetProviderId(MetadataProviders.MusicBrainzArtist, GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Artist Id")));
+
+            audio.SetProviderId(MetadataProviders.MusicBrainzAlbum, GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Album Id")));
+            audio.SetProviderId(MetadataProviders.MusicBrainzReleaseGroup, GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Release Group Id")));
+            audio.SetProviderId(MetadataProviders.MusicBrainzTrack, GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Release Track Id")));
+        }
+
+        private string GetMultipleMusicBrainzId(string value)
+        {
+            if (string.IsNullOrWhiteSpace(value))
+            {
+                return null;
+            }
+
+            return value.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries)
+                .Select(i => i.Trim())
+                .FirstOrDefault(i => !string.IsNullOrWhiteSpace(i));
+        }
+
+        private readonly char[] _nameDelimiters = { '/', '|', ';', '\\' };
+
+        /// <summary>
+        /// Splits the specified val.
+        /// </summary>
+        /// <param name="val">The val.</param>
+        /// <param name="allowCommaDelimiter">if set to <c>true</c> [allow comma delimiter].</param>
+        /// <returns>System.String[][].</returns>
+        private IEnumerable<string> Split(string val, bool allowCommaDelimiter)
+        {
+            // Only use the comma as a delimeter if there are no slashes or pipes. 
+            // We want to be careful not to split names that have commas in them
+            var delimeter = !allowCommaDelimiter || _nameDelimiters.Any(i => val.IndexOf(i) != -1) ?
+                _nameDelimiters :
+                new[] { ',' };
+
+            return val.Split(delimeter, StringSplitOptions.RemoveEmptyEntries)
+                .Where(i => !string.IsNullOrWhiteSpace(i))
+                .Select(i => i.Trim());
+        }
+
+        private const string ArtistReplaceValue = " | ";
+
+        private IEnumerable<string> SplitArtists(string val)
+        {
+            val = val.Replace(" featuring ", ArtistReplaceValue, StringComparison.OrdinalIgnoreCase)
+                .Replace(" feat. ", ArtistReplaceValue, StringComparison.OrdinalIgnoreCase);
+
+            var artistsFound = new List<string>();
+
+            foreach (var whitelistArtist in GetSplitWhitelist())
+            {
+                var originalVal = val;
+                val = val.Replace(whitelistArtist, "|", StringComparison.OrdinalIgnoreCase);
+
+                if (!string.Equals(originalVal, val, StringComparison.OrdinalIgnoreCase))
+                {
+                    artistsFound.Add(whitelistArtist);
+                }
+            }
+
+            // Only use the comma as a delimeter if there are no slashes or pipes. 
+            // We want to be careful not to split names that have commas in them
+            var delimeter = _nameDelimiters;
+
+            var artists = val.Split(delimeter, StringSplitOptions.RemoveEmptyEntries)
+                .Where(i => !string.IsNullOrWhiteSpace(i))
+                .Select(i => i.Trim());
+
+            artistsFound.AddRange(artists);
+            return artistsFound;
+        }
+
+
+        private List<string> _splitWhiteList = null;
+
+        private IEnumerable<string> GetSplitWhitelist()
+        {
+            if (_splitWhiteList == null)
+            {
+                var file = GetType().Namespace + ".whitelist.txt";
+
+                using (var stream = GetType().Assembly.GetManifestResourceStream(file))
+                {
+                    using (var reader = new StreamReader(stream))
+                    {
+                        var list = new List<string>();
+
+                        while (!reader.EndOfStream)
+                        {
+                            var val = reader.ReadLine();
+
+                            if (!string.IsNullOrWhiteSpace(val))
+                            {
+                                list.Add(val);
+                            }
+                        }
+
+                        _splitWhiteList = list;
+                    }
+                }
+            }
+
+            return _splitWhiteList;
+        }
+
+        /// <summary>
+        /// Gets the studios from the tags collection
+        /// </summary>
+        /// <param name="audio">The audio.</param>
+        /// <param name="tags">The tags.</param>
+        /// <param name="tagName">Name of the tag.</param>
+        private void FetchStudios(Model.Entities.MediaInfo audio, Dictionary<string, string> tags, string tagName)
+        {
+            var val = FFProbeHelpers.GetDictionaryValue(tags, tagName);
+
+            if (!string.IsNullOrEmpty(val))
+            {
+                var studios = Split(val, true);
+
+                foreach (var studio in studios)
+                {
+                    // Sometimes the artist name is listed here, account for that
+                    if (audio.Artists.Contains(studio, StringComparer.OrdinalIgnoreCase))
+                    {
+                        continue;
+                    }
+                    if (audio.AlbumArtists.Contains(studio, StringComparer.OrdinalIgnoreCase))
+                    {
+                        continue;
+                    }
+
+                    audio.Studios.Add(studio);
+                }
+
+                audio.Studios = audio.Studios
+                    .Where(i => !string.IsNullOrWhiteSpace(i))
+                    .Distinct(StringComparer.OrdinalIgnoreCase)
+                    .ToList();
+            }
+        }
+
+        /// <summary>
+        /// Gets the genres from the tags collection
+        /// </summary>
+        /// <param name="info">The information.</param>
+        /// <param name="tags">The tags.</param>
+        private void FetchGenres(Model.Entities.MediaInfo info, Dictionary<string, string> tags)
+        {
+            var val = FFProbeHelpers.GetDictionaryValue(tags, "genre");
+
+            if (!string.IsNullOrEmpty(val))
+            {
+                foreach (var genre in Split(val, true))
+                {
+                    info.Genres.Add(genre);
+                }
+
+                info.Genres = info.Genres
+                    .Where(i => !string.IsNullOrWhiteSpace(i))
+                    .Distinct(StringComparer.OrdinalIgnoreCase)
+                    .ToList();
+            }
+        }
+
+        /// <summary>
+        /// Gets the disc number, which is sometimes can be in the form of '1', or '1/3'
+        /// </summary>
+        /// <param name="tags">The tags.</param>
+        /// <param name="tagName">Name of the tag.</param>
+        /// <returns>System.Nullable{System.Int32}.</returns>
+        private int? GetDictionaryDiscValue(Dictionary<string, string> tags, string tagName)
+        {
+            var disc = FFProbeHelpers.GetDictionaryValue(tags, tagName);
+
+            if (!string.IsNullOrEmpty(disc))
+            {
+                disc = disc.Split('/')[0];
+
+                int num;
+
+                if (int.TryParse(disc, out num))
+                {
+                    return num;
+                }
+            }
+
+            return null;
+        }
+
+        private ChapterInfo GetChapterInfo(MediaChapter chapter)
+        {
+            var info = new ChapterInfo();
+
+            if (chapter.tags != null)
+            {
+                string name;
+                if (chapter.tags.TryGetValue("title", out name))
+                {
+                    info.Name = name;
+                }
+            }
+
+            // Limit accuracy to milliseconds to match xml saving
+            var secondsString = chapter.start_time;
+            double seconds;
+
+            if (double.TryParse(secondsString, NumberStyles.Any, CultureInfo.InvariantCulture, out seconds))
+            {
+                var ms = Math.Round(TimeSpan.FromSeconds(seconds).TotalMilliseconds);
+                info.StartPositionTicks = TimeSpan.FromMilliseconds(ms).Ticks;
+            }
+
+            return info;
+        }
+
+        private const int MaxSubtitleDescriptionExtractionLength = 100; // When extracting subtitles, the maximum length to consider (to avoid invalid filenames)
+
+        private void FetchWtvInfo(Model.Entities.MediaInfo video, InternalMediaInfoResult data)
+        {
+            if (data.format == null || data.format.tags == null)
+            {
+                return;
+            }
+
+            var genres = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/Genre");
+
+            if (!string.IsNullOrWhiteSpace(genres))
+            {
+                //genres = FFProbeHelpers.GetDictionaryValue(data.format.tags, "genre");
+            }
+
+            if (!string.IsNullOrWhiteSpace(genres))
+            {
+                video.Genres = genres.Split(new[] { ';', '/', ',' }, StringSplitOptions.RemoveEmptyEntries)
+                    .Where(i => !string.IsNullOrWhiteSpace(i))
+                    .Select(i => i.Trim())
+                    .ToList();
+            }
+
+            var officialRating = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/ParentalRating");
+
+            if (!string.IsNullOrWhiteSpace(officialRating))
+            {
+                video.OfficialRating = officialRating;
+            }
+
+            var people = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/MediaCredits");
+
+            if (!string.IsNullOrEmpty(people))
+            {
+                video.People = people.Split(new[] { ';', '/' }, StringSplitOptions.RemoveEmptyEntries)
+                    .Where(i => !string.IsNullOrWhiteSpace(i))
+                    .Select(i => new BaseItemPerson { Name = i.Trim(), Type = PersonType.Actor })
+                    .ToList();
+            }
+
+            var year = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/OriginalReleaseTime");
+            if (!string.IsNullOrWhiteSpace(year))
+            {
+                int val;
+
+                if (int.TryParse(year, NumberStyles.Integer, _usCulture, out val))
+                {
+                    video.ProductionYear = val;
+                }
+            }
+
+            var premiereDateString = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/MediaOriginalBroadcastDateTime");
+            if (!string.IsNullOrWhiteSpace(premiereDateString))
+            {
+                DateTime val;
+
+                // Credit to MCEBuddy: https://mcebuddy2x.codeplex.com/
+                // DateTime is reported along with timezone info (typically Z i.e. UTC hence assume None)
+                if (DateTime.TryParse(year, null, DateTimeStyles.None, out val))
+                {
+                    video.PremiereDate = val.ToUniversalTime();
+                }
+            }
+
+            var description = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/SubTitleDescription");
+
+            var subTitle = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/SubTitle");
+
+            // For below code, credit to MCEBuddy: https://mcebuddy2x.codeplex.com/
+
+            // Sometimes for TV Shows the Subtitle field is empty and the subtitle description contains the subtitle, extract if possible. See ticket https://mcebuddy2x.codeplex.com/workitem/1910
+            // The format is -> EPISODE/TOTAL_EPISODES_IN_SEASON. SUBTITLE: DESCRIPTION
+            // OR -> COMMENT. SUBTITLE: DESCRIPTION
+            // e.g. -> 4/13. The Doctor's Wife: Science fiction drama. When he follows a Time Lord distress signal, the Doctor puts Amy, Rory and his beloved TARDIS in grave danger. Also in HD. [AD,S]
+            // e.g. -> CBeebies Bedtime Hour. The Mystery: Animated adventures of two friends who live on an island in the middle of the big city. Some of Abney and Teal's favourite objects are missing. [S]
+            if (String.IsNullOrWhiteSpace(subTitle) && !String.IsNullOrWhiteSpace(description) && description.Substring(0, Math.Min(description.Length, MaxSubtitleDescriptionExtractionLength)).Contains(":")) // Check within the Subtitle size limit, otherwise from description it can get too long creating an invalid filename
+            {
+                string[] parts = description.Split(':');
+                if (parts.Length > 0)
+                {
+                    string subtitle = parts[0];
+                    try
+                    {
+                        if (subtitle.Contains("/")) // It contains a episode number and season number
+                        {
+                            string[] numbers = subtitle.Split(' ');
+                            video.IndexNumber = int.Parse(numbers[0].Replace(".", "").Split('/')[0]);
+                            int totalEpisodesInSeason = int.Parse(numbers[0].Replace(".", "").Split('/')[1]);
+
+                            description = String.Join(" ", numbers, 1, numbers.Length - 1).Trim(); // Skip the first, concatenate the rest, clean up spaces and save it
+                        }
+                        else
+                            throw new Exception(); // Switch to default parsing
+                    }
+                    catch // Default parsing
+                    {
+                        if (subtitle.Contains(".")) // skip the comment, keep the subtitle
+                            description = String.Join(".", subtitle.Split('.'), 1, subtitle.Split('.').Length - 1).Trim(); // skip the first
+                        else
+                            description = subtitle.Trim(); // Clean up whitespaces and save it
+                    }
+                }
+            }
+
+            if (!string.IsNullOrWhiteSpace(description))
+            {
+                video.Overview = description;
+            }
+        }
+
+        private void ExtractTimestamp(Model.Entities.MediaInfo video)
+        {
+            if (video.VideoType == VideoType.VideoFile)
+            {
+                if (string.Equals(video.Container, "mpeg2ts", StringComparison.OrdinalIgnoreCase) ||
+                    string.Equals(video.Container, "m2ts", StringComparison.OrdinalIgnoreCase) ||
+                    string.Equals(video.Container, "ts", StringComparison.OrdinalIgnoreCase))
+                {
+                    try
+                    {
+                        video.Timestamp = GetMpegTimestamp(video.Path);
+
+                        _logger.Debug("Video has {0} timestamp", video.Timestamp);
+                    }
+                    catch (Exception ex)
+                    {
+                        _logger.ErrorException("Error extracting timestamp info from {0}", ex, video.Path);
+                        video.Timestamp = null;
+                    }
+                }
+            }
+        }
+
+        private TransportStreamTimestamp GetMpegTimestamp(string path)
+        {
+            var packetBuffer = new byte['Å'];
+
+            using (var fs = _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
+            {
+                fs.Read(packetBuffer, 0, packetBuffer.Length);
+            }
+
+            if (packetBuffer[0] == 71)
+            {
+                return TransportStreamTimestamp.None;
+            }
+
+            if ((packetBuffer[4] == 71) && (packetBuffer['Ä'] == 71))
+            {
+                if ((packetBuffer[0] == 0) && (packetBuffer[1] == 0) && (packetBuffer[2] == 0) && (packetBuffer[3] == 0))
+                {
+                    return TransportStreamTimestamp.Zero;
+                }
+
+                return TransportStreamTimestamp.Valid;
+            }
+
+            return TransportStreamTimestamp.None;
+        }
+
+        private void UpdateFromMediaInfo(MediaSourceInfo video, MediaStream videoStream)
+        {
+            if (video.VideoType == VideoType.VideoFile && video.Protocol == MediaProtocol.File)
+            {
+                if (videoStream != null)
+                {
+                    try
+                    {
+                        var result = new MediaInfoLib().GetVideoInfo(video.Path);
+
+                        videoStream.IsCabac = result.IsCabac ?? videoStream.IsCabac;
+                        videoStream.IsInterlaced = result.IsInterlaced ?? videoStream.IsInterlaced;
+                        videoStream.BitDepth = result.BitDepth ?? videoStream.BitDepth;
+                        videoStream.RefFrames = result.RefFrames;
+                    }
+                    catch (Exception ex)
+                    {
+                        _logger.ErrorException("Error running MediaInfo on {0}", ex, video.Path);
+                    }
+                }
+            }
+        }
+    }
+}

+ 40 - 3
MediaBrowser.Model/Dlna/StreamBuilder.cs

@@ -1,6 +1,7 @@
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Extensions;
+using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.MediaInfo;
 using MediaBrowser.Model.Session;
 using System;
@@ -11,13 +12,16 @@ namespace MediaBrowser.Model.Dlna
     public class StreamBuilder
     {
         private readonly ILocalPlayer _localPlayer;
+        private readonly ILogger _logger;
 
-        public StreamBuilder(ILocalPlayer localPlayer)
+        public StreamBuilder(ILocalPlayer localPlayer, ILogger logger)
         {
             _localPlayer = localPlayer;
+            _logger = logger;
         }
-        public StreamBuilder()
-            : this(new NullLocalPlayer())
+
+        public StreamBuilder(ILogger logger)
+            : this(new NullLocalPlayer(), logger)
         {
         }
 
@@ -353,6 +357,12 @@ namespace MediaBrowser.Model.Dlna
             bool isEligibleForDirectPlay = IsEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options), subtitleStream, options);
             bool isEligibleForDirectStream = IsEligibleForDirectPlay(item, options.GetMaxBitrate(), subtitleStream, options);
 
+            _logger.Debug("Profile: {0}, Path: {1}, isEligibleForDirectPlay: {2}, isEligibleForDirectStream: {3}",
+                options.Profile.Name ?? "Unknown Profile",
+                item.Path ?? "Unknown path",
+                isEligibleForDirectPlay,
+                isEligibleForDirectStream);
+
             if (isEligibleForDirectPlay || isEligibleForDirectStream)
             {
                 // See if it can be direct played
@@ -504,6 +514,10 @@ namespace MediaBrowser.Model.Dlna
 
             if (directPlay == null)
             {
+                _logger.Debug("Profile: {0}, No direct play profiles found for Path: {1}",
+                    profile.Name ?? "Unknown Profile",
+                    mediaSource.Path ?? "Unknown path"); 
+                
                 return null;
             }
 
@@ -550,6 +564,11 @@ namespace MediaBrowser.Model.Dlna
             {
                 if (!conditionProcessor.IsVideoConditionSatisfied(i, audioBitrate, audioChannels, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isCabac, refFrames, numVideoStreams, numAudioStreams))
                 {
+                    _logger.Debug("Profile: {0}, DirectPlay=false. Reason=VideoContainerProfile.{1} Path: {2}",
+                        profile.Name ?? "Unknown Profile",
+                        i.Property,
+                        mediaSource.Path ?? "Unknown path");
+
                     return null;
                 }
             }
@@ -558,6 +577,10 @@ namespace MediaBrowser.Model.Dlna
 
             if (string.IsNullOrEmpty(videoCodec))
             {
+                _logger.Debug("Profile: {0}, DirectPlay=false. Reason=Unknown video codec. Path: {1}",
+                    profile.Name ?? "Unknown Profile",
+                    mediaSource.Path ?? "Unknown path");
+
                 return null;
             }
 
@@ -577,6 +600,11 @@ namespace MediaBrowser.Model.Dlna
             {
                 if (!conditionProcessor.IsVideoConditionSatisfied(i, audioBitrate, audioChannels, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isCabac, refFrames, numVideoStreams, numAudioStreams))
                 {
+                    _logger.Debug("Profile: {0}, DirectPlay=false. Reason=VideoCodecProfile.{1} Path: {2}",
+                        profile.Name ?? "Unknown Profile",
+                        i.Property,
+                        mediaSource.Path ?? "Unknown path");
+
                     return null;
                 }
             }
@@ -587,6 +615,10 @@ namespace MediaBrowser.Model.Dlna
 
                 if (string.IsNullOrEmpty(audioCodec))
                 {
+                    _logger.Debug("Profile: {0}, DirectPlay=false. Reason=Unknown audio codec. Path: {1}",
+                        profile.Name ?? "Unknown Profile",
+                        mediaSource.Path ?? "Unknown path");
+
                     return null;
                 }
 
@@ -607,6 +639,11 @@ namespace MediaBrowser.Model.Dlna
                     bool? isSecondaryAudio = audioStream == null ? null : mediaSource.IsSecondaryAudio(audioStream);
                     if (!conditionProcessor.IsVideoAudioConditionSatisfied(i, audioChannels, audioBitrate, audioProfile, isSecondaryAudio))
                     {
+                        _logger.Debug("Profile: {0}, DirectPlay=false. Reason=VideoAudioCodecProfile.{1} Path: {2}",
+                            profile.Name ?? "Unknown Profile",
+                            i.Property,
+                            mediaSource.Path ?? "Unknown path");
+                        
                         return null;
                     }
                 }

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

@@ -70,6 +70,7 @@ namespace MediaBrowser.Model.Dlna
         public string SubtitleFormat { get; set; }
 
         public string PlaySessionId { get; set; }
+        public List<MediaSourceInfo> AllMediaSources { get; set; }
 
         public string MediaSourceId
         {

+ 2 - 2
MediaBrowser.Model/Dto/BaseItemPerson.cs

@@ -1,7 +1,7 @@
-using System.ComponentModel;
+using MediaBrowser.Model.Extensions;
+using System.ComponentModel;
 using System.Diagnostics;
 using System.Runtime.Serialization;
-using MediaBrowser.Model.Extensions;
 
 namespace MediaBrowser.Model.Dto
 {

+ 50 - 11
MediaBrowser.Model/Entities/MediaInfo.cs

@@ -1,26 +1,65 @@
+using MediaBrowser.Model.Dto;
+using System;
 using System.Collections.Generic;
 
 namespace MediaBrowser.Model.Entities
 {
-    public class MediaInfo
+    public class MediaInfo : MediaSourceInfo, IHasProviderIds
     {
+        public List<ChapterInfo> Chapters { get; set; }
+
         /// <summary>
-        /// Gets or sets the media streams.
+        /// Gets or sets the title.
         /// </summary>
-        /// <value>The media streams.</value>
-        public List<MediaStream> MediaStreams { get; set; }
-
+        /// <value>The title.</value>
+        public string Title { get; set; }
         /// <summary>
-        /// Gets or sets the format.
+        /// Gets or sets the album.
         /// </summary>
-        /// <value>The format.</value>
-        public string Format { get; set; }
-
-        public int? TotalBitrate { get; set; }
+        /// <value>The album.</value>
+        public string Album { get; set; }
+        /// <summary>
+        /// Gets or sets the artists.
+        /// </summary>
+        /// <value>The artists.</value>
+        public List<string> Artists { get; set; }
+        /// <summary>
+        /// Gets or sets the album artists.
+        /// </summary>
+        /// <value>The album artists.</value>
+        public List<string> AlbumArtists { get; set; }
+        /// <summary>
+        /// Gets or sets the studios.
+        /// </summary>
+        /// <value>The studios.</value>
+        public List<string> Studios { get; set; }
+        public List<string> Genres { get; set; }
+        public int? IndexNumber { get; set; }
+        public int? ParentIndexNumber { get; set; }
+        public int? ProductionYear { get; set; }
+        public DateTime? PremiereDate { get; set; }
+        public List<BaseItemPerson> People { get; set; }
+        public Dictionary<string, string> ProviderIds { get; set; }
+        /// <summary>
+        /// Gets or sets the official rating.
+        /// </summary>
+        /// <value>The official rating.</value>
+        public string OfficialRating { get; set; }
+        /// <summary>
+        /// Gets or sets the overview.
+        /// </summary>
+        /// <value>The overview.</value>
+        public string Overview { get; set; }
 
         public MediaInfo()
         {
-            MediaStreams = new List<MediaStream>();
+            Chapters = new List<ChapterInfo>();
+            Artists = new List<string>();
+            AlbumArtists = new List<string>();
+            Studios = new List<string>();
+            Genres = new List<string>();
+            People = new List<BaseItemPerson>();
+            ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
         }
     }
 }

+ 0 - 5
MediaBrowser.Providers/MediaBrowser.Providers.csproj

@@ -102,7 +102,6 @@
     <Compile Include="Manager\MetadataService.cs" />
     <Compile Include="Manager\SeriesOrderManager.cs" />
     <Compile Include="MediaInfo\FFProbeAudioInfo.cs" />
-    <Compile Include="MediaInfo\FFProbeHelpers.cs" />
     <Compile Include="MediaInfo\FFProbeProvider.cs" />
     <Compile Include="MediaInfo\FFProbeVideoInfo.cs" />
     <Compile Include="MediaInfo\SubtitleDownloader.cs" />
@@ -198,10 +197,6 @@
       <Project>{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}</Project>
       <Name>MediaBrowser.Controller</Name>
     </ProjectReference>
-    <ProjectReference Include="..\MediaBrowser.MediaInfo\MediaBrowser.MediaInfo.csproj">
-      <Project>{6e4145e4-c6d4-4e4d-94f2-87188db6e239}</Project>
-      <Name>MediaBrowser.MediaInfo</Name>
-    </ProjectReference>
     <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj">
       <Project>{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}</Project>
       <Name>MediaBrowser.Model</Name>

+ 39 - 303
MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs

@@ -1,5 +1,4 @@
 using MediaBrowser.Common.Configuration;
-using MediaBrowser.Model.Extensions;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Library;
@@ -8,8 +7,6 @@ using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.MediaInfo;
 using MediaBrowser.Model.Serialization;
-using System;
-using System.Collections.Generic;
 using System.Globalization;
 using System.IO;
 using System.Linq;
@@ -49,18 +46,14 @@ namespace MediaBrowser.Providers.MediaInfo
 
             cancellationToken.ThrowIfCancellationRequested();
 
-            FFProbeHelpers.NormalizeFFProbeResult(result);
-
-            cancellationToken.ThrowIfCancellationRequested();
-
             await Fetch(item, cancellationToken, result).ConfigureAwait(false);
 
             return ItemUpdateType.MetadataImport;
         }
 
-        private const string SchemaVersion = "1";
+        private const string SchemaVersion = "2";
 
-        private async Task<InternalMediaInfoResult> GetMediaInfo(BaseItem item, CancellationToken cancellationToken)
+        private async Task<Model.Entities.MediaInfo> GetMediaInfo(BaseItem item, CancellationToken cancellationToken)
         {
             cancellationToken.ThrowIfCancellationRequested();
 
@@ -71,7 +64,7 @@ namespace MediaBrowser.Providers.MediaInfo
 
             try
             {
-                return _json.DeserializeFromFile<InternalMediaInfoResult>(cachePath);
+                return _json.DeserializeFromFile<Model.Entities.MediaInfo>(cachePath);
             }
             catch (FileNotFoundException)
             {
@@ -83,7 +76,7 @@ namespace MediaBrowser.Providers.MediaInfo
 
             var inputPath = new[] { item.Path };
 
-            var result = await _mediaEncoder.GetMediaInfo(inputPath, MediaProtocol.File, false, cancellationToken).ConfigureAwait(false);
+            var result = await _mediaEncoder.GetMediaInfo(inputPath, item.Path, MediaProtocol.File, true, false, cancellationToken).ConfigureAwait(false);
 
             Directory.CreateDirectory(Path.GetDirectoryName(cachePath));
             _json.SerializeToFile(result, cachePath);
@@ -96,61 +89,23 @@ namespace MediaBrowser.Providers.MediaInfo
         /// </summary>
         /// <param name="audio">The audio.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
-        /// <param name="data">The data.</param>
+        /// <param name="mediaInfo">The media information.</param>
         /// <returns>Task.</returns>
-        protected Task Fetch(Audio audio, CancellationToken cancellationToken, InternalMediaInfoResult data)
+        protected Task Fetch(Audio audio, CancellationToken cancellationToken, Model.Entities.MediaInfo mediaInfo)
         {
-            var mediaInfo = MediaEncoderHelpers.GetMediaInfo(data);
             var mediaStreams = mediaInfo.MediaStreams;
 
-            audio.FormatName = mediaInfo.Format;
-            audio.TotalBitrate = mediaInfo.TotalBitrate;
+            audio.FormatName = mediaInfo.Container;
+            audio.TotalBitrate = mediaInfo.Bitrate;
             audio.HasEmbeddedImage = mediaStreams.Any(i => i.Type == MediaStreamType.EmbeddedImage);
 
-            if (data.streams != null)
-            {
-                // Get the first audio stream
-                var stream = data.streams.FirstOrDefault(s => string.Equals(s.codec_type, "audio", StringComparison.OrdinalIgnoreCase));
+            audio.RunTimeTicks = mediaInfo.RunTimeTicks;
+            audio.Size = mediaInfo.Size;
 
-                if (stream != null)
-                {
-                    // Get duration from stream properties
-                    var duration = stream.duration;
+            var extension = (Path.GetExtension(audio.Path) ?? string.Empty).TrimStart('.');
+            audio.Container = extension;
 
-                    // If it's not there go into format properties
-                    if (string.IsNullOrEmpty(duration))
-                    {
-                        duration = data.format.duration;
-                    }
-
-                    // If we got something, parse it
-                    if (!string.IsNullOrEmpty(duration))
-                    {
-                        audio.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(duration, _usCulture)).Ticks;
-                    }
-                }
-            }
-
-            if (data.format != null)
-            {
-                var extension = (Path.GetExtension(audio.Path) ?? string.Empty).TrimStart('.');
-
-                audio.Container = extension;
-
-                if (!string.IsNullOrEmpty(data.format.size))
-                {
-                    audio.Size = long.Parse(data.format.size, _usCulture);
-                }
-                else
-                {
-                    audio.Size = null;
-                }
-
-                if (data.format.tags != null)
-                {
-                    FetchDataFromTags(audio, data.format.tags);
-                }
-            }
+            FetchDataFromTags(audio, mediaInfo);
 
             return _itemRepo.SaveMediaStreams(audio.Id, mediaStreams, cancellationToken);
         }
@@ -159,92 +114,36 @@ namespace MediaBrowser.Providers.MediaInfo
         /// Fetches data from the tags dictionary
         /// </summary>
         /// <param name="audio">The audio.</param>
-        /// <param name="tags">The tags.</param>
-        private void FetchDataFromTags(Audio audio, Dictionary<string, string> tags)
+        /// <param name="data">The data.</param>
+        private void FetchDataFromTags(Audio audio, Model.Entities.MediaInfo data)
         {
-            var title = FFProbeHelpers.GetDictionaryValue(tags, "title");
-
             // Only set Name if title was found in the dictionary
-            if (!string.IsNullOrEmpty(title))
+            if (!string.IsNullOrEmpty(data.Title))
             {
-                audio.Name = title;
+                audio.Name = data.Title;
             }
 
             if (!audio.LockedFields.Contains(MetadataFields.Cast))
             {
                 audio.People.Clear();
 
-                var composer = FFProbeHelpers.GetDictionaryValue(tags, "composer");
-
-                if (!string.IsNullOrWhiteSpace(composer))
+                foreach (var person in data.People)
                 {
-                    foreach (var person in Split(composer, false))
+                    audio.AddPerson(new PersonInfo
                     {
-                        audio.AddPerson(new PersonInfo { Name = person, Type = PersonType.Composer });
-                    }
-                }
-            }
-
-            audio.Album = FFProbeHelpers.GetDictionaryValue(tags, "album");
-
-            var artists = FFProbeHelpers.GetDictionaryValue(tags, "artists");
-
-            if (!string.IsNullOrWhiteSpace(artists))
-            {
-                audio.Artists = artists.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries)
-                    .Distinct(StringComparer.OrdinalIgnoreCase)
-                    .ToList();
-            }
-            else
-            {
-                var artist = FFProbeHelpers.GetDictionaryValue(tags, "artist");
-                if (string.IsNullOrWhiteSpace(artist))
-                {
-                    audio.Artists.Clear();
+                        Name = person.Name,
+                        Type = person.Type,
+                        Role = person.Role
+                    });
                 }
-                else
-                {
-                    audio.Artists = SplitArtists(artist)
-                        .Distinct(StringComparer.OrdinalIgnoreCase)
-                        .ToList();
-                }
-            }
-
-            var albumArtist = FFProbeHelpers.GetDictionaryValue(tags, "albumartist");
-            if (string.IsNullOrWhiteSpace(albumArtist))
-            {
-                albumArtist = FFProbeHelpers.GetDictionaryValue(tags, "album artist");
-            }
-            if (string.IsNullOrWhiteSpace(albumArtist))
-            {
-                albumArtist = FFProbeHelpers.GetDictionaryValue(tags, "album_artist");
             }
 
-            if (string.IsNullOrWhiteSpace(albumArtist))
-            {
-                audio.AlbumArtists = new List<string>();
-            }
-            else
-            {
-                audio.AlbumArtists = SplitArtists(albumArtist)
-                    .Distinct(StringComparer.OrdinalIgnoreCase)
-                    .ToList();
-
-            }
-
-            // Track number
-            audio.IndexNumber = GetDictionaryDiscValue(tags, "track");
-
-            // Disc number
-            audio.ParentIndexNumber = GetDictionaryDiscValue(tags, "disc");
-
-            audio.ProductionYear = FFProbeHelpers.GetDictionaryNumericValue(tags, "date");
-
-            // Several different forms of retaildate
-            audio.PremiereDate = FFProbeHelpers.GetDictionaryDateTime(tags, "retaildate") ??
-                FFProbeHelpers.GetDictionaryDateTime(tags, "retail date") ??
-                FFProbeHelpers.GetDictionaryDateTime(tags, "retail_date") ??
-                FFProbeHelpers.GetDictionaryDateTime(tags, "date");
+            audio.Album = data.Album;
+            audio.Artists = data.Artists;
+            audio.AlbumArtists = data.AlbumArtists;
+            audio.IndexNumber = data.IndexNumber;
+            audio.ProductionYear = data.ProductionYear;
+            audio.PremiereDate = data.PremiereDate;
 
             // If we don't have a ProductionYear try and get it from PremiereDate
             if (audio.PremiereDate.HasValue && !audio.ProductionYear.HasValue)
@@ -253,193 +152,30 @@ namespace MediaBrowser.Providers.MediaInfo
             }
 
             if (!audio.LockedFields.Contains(MetadataFields.Genres))
-            {
-                FetchGenres(audio, tags);
-            }
-
-            if (!audio.LockedFields.Contains(MetadataFields.Studios))
-            {
-                audio.Studios.Clear();
-
-                // There's several values in tags may or may not be present
-                FetchStudios(audio, tags, "organization");
-                FetchStudios(audio, tags, "ensemble");
-                FetchStudios(audio, tags, "publisher");
-            }
-
-            // These support mulitple values, but for now we only store the first.
-            audio.SetProviderId(MetadataProviders.MusicBrainzAlbumArtist, GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Album Artist Id")));
-            audio.SetProviderId(MetadataProviders.MusicBrainzArtist, GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Artist Id")));
-
-            audio.SetProviderId(MetadataProviders.MusicBrainzAlbum, GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Album Id")));
-            audio.SetProviderId(MetadataProviders.MusicBrainzReleaseGroup, GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Release Group Id")));
-            audio.SetProviderId(MetadataProviders.MusicBrainzTrack, GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Release Track Id")));
-        }
-
-        private string GetMultipleMusicBrainzId(string value)
-        {
-            if (string.IsNullOrWhiteSpace(value))
-            {
-                return null;
-            }
-
-            return value.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries)
-                .Select(i => i.Trim())
-                .FirstOrDefault(i => !string.IsNullOrWhiteSpace(i));
-        }
-
-        private readonly char[] _nameDelimiters = { '/', '|', ';', '\\' };
-
-        /// <summary>
-        /// Splits the specified val.
-        /// </summary>
-        /// <param name="val">The val.</param>
-        /// <param name="allowCommaDelimiter">if set to <c>true</c> [allow comma delimiter].</param>
-        /// <returns>System.String[][].</returns>
-        private IEnumerable<string> Split(string val, bool allowCommaDelimiter)
-        {
-            // Only use the comma as a delimeter if there are no slashes or pipes. 
-            // We want to be careful not to split names that have commas in them
-            var delimeter = !allowCommaDelimiter || _nameDelimiters.Any(i => val.IndexOf(i) != -1) ?
-                _nameDelimiters :
-                new[] { ',' };
-
-            return val.Split(delimeter, StringSplitOptions.RemoveEmptyEntries)
-                .Where(i => !string.IsNullOrWhiteSpace(i))
-                .Select(i => i.Trim());
-        }
-
-        private const string ArtistReplaceValue = " | ";
-
-        private IEnumerable<string> SplitArtists(string val)
-        {
-            val = val.Replace(" featuring ", ArtistReplaceValue, StringComparison.OrdinalIgnoreCase)
-                .Replace(" feat. ", ArtistReplaceValue, StringComparison.OrdinalIgnoreCase);
-
-            var artistsFound = new List<string>();
-
-            foreach (var whitelistArtist in GetSplitWhitelist())
-            {
-                var originalVal = val;
-                val = val.Replace(whitelistArtist, "|", StringComparison.OrdinalIgnoreCase);
-
-                if (!string.Equals(originalVal, val, StringComparison.OrdinalIgnoreCase))
-                {
-                    artistsFound.Add(whitelistArtist);
-                }
-            }
-
-            // Only use the comma as a delimeter if there are no slashes or pipes. 
-            // We want to be careful not to split names that have commas in them
-            var delimeter = _nameDelimiters;
-
-            var artists = val.Split(delimeter, StringSplitOptions.RemoveEmptyEntries)
-                .Where(i => !string.IsNullOrWhiteSpace(i))
-                .Select(i => i.Trim());
-
-            artistsFound.AddRange(artists);
-            return artistsFound;
-        }
-
-
-        private List<string> _splitWhiteList = null;
-
-        private IEnumerable<string> GetSplitWhitelist()
-        {
-            if (_splitWhiteList == null)
-            {
-                var file = GetType().Namespace + ".whitelist.txt";
-
-                using (var stream = GetType().Assembly.GetManifestResourceStream(file))
-                {
-                    using (var reader = new StreamReader(stream))
-                    {
-                        var list = new List<string>();
-
-                        while (!reader.EndOfStream)
-                        {
-                            var val = reader.ReadLine();
-
-                            if (!string.IsNullOrWhiteSpace(val))
-                            {
-                                list.Add(val);
-                            }
-                        }
-
-                        _splitWhiteList = list;
-                    }
-                }
-            }
-
-            return _splitWhiteList;
-        }
-
-        /// <summary>
-        /// Gets the studios from the tags collection
-        /// </summary>
-        /// <param name="audio">The audio.</param>
-        /// <param name="tags">The tags.</param>
-        /// <param name="tagName">Name of the tag.</param>
-        private void FetchStudios(Audio audio, Dictionary<string, string> tags, string tagName)
-        {
-            var val = FFProbeHelpers.GetDictionaryValue(tags, tagName);
-
-            if (!string.IsNullOrEmpty(val))
-            {
-                // Sometimes the artist name is listed here, account for that
-                var studios = Split(val, true).Where(i => !audio.HasAnyArtist(i));
-
-                foreach (var studio in studios)
-                {
-                    audio.AddStudio(studio);
-                }
-            }
-        }
-
-        /// <summary>
-        /// Gets the genres from the tags collection
-        /// </summary>
-        /// <param name="audio">The audio.</param>
-        /// <param name="tags">The tags.</param>
-        private void FetchGenres(Audio audio, Dictionary<string, string> tags)
-        {
-            var val = FFProbeHelpers.GetDictionaryValue(tags, "genre");
-
-            if (!string.IsNullOrEmpty(val))
             {
                 audio.Genres.Clear();
 
-                foreach (var genre in Split(val, true))
+                foreach (var genre in data.Genres)
                 {
                     audio.AddGenre(genre);
                 }
             }
-        }
-
-        /// <summary>
-        /// Gets the disc number, which is sometimes can be in the form of '1', or '1/3'
-        /// </summary>
-        /// <param name="tags">The tags.</param>
-        /// <param name="tagName">Name of the tag.</param>
-        /// <returns>System.Nullable{System.Int32}.</returns>
-        private int? GetDictionaryDiscValue(Dictionary<string, string> tags, string tagName)
-        {
-            var disc = FFProbeHelpers.GetDictionaryValue(tags, tagName);
 
-            if (!string.IsNullOrEmpty(disc))
+            if (!audio.LockedFields.Contains(MetadataFields.Studios))
             {
-                disc = disc.Split('/')[0];
-
-                int num;
+                audio.Studios.Clear();
 
-                if (int.TryParse(disc, out num))
+                foreach (var studio in data.Studios)
                 {
-                    return num;
+                    audio.AddStudio(studio);
                 }
             }
 
-            return null;
+            audio.SetProviderId(MetadataProviders.MusicBrainzAlbumArtist, data.GetProviderId(MetadataProviders.MusicBrainzAlbumArtist));
+            audio.SetProviderId(MetadataProviders.MusicBrainzArtist, data.GetProviderId(MetadataProviders.MusicBrainzArtist));
+            audio.SetProviderId(MetadataProviders.MusicBrainzAlbum, data.GetProviderId(MetadataProviders.MusicBrainzAlbum));
+            audio.SetProviderId(MetadataProviders.MusicBrainzReleaseGroup, data.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup));
+            audio.SetProviderId(MetadataProviders.MusicBrainzTrack, data.GetProviderId(MetadataProviders.MusicBrainzTrack));
         }
-
     }
 }

+ 63 - 234
MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs

@@ -13,7 +13,6 @@ using MediaBrowser.Controller.MediaEncoding;
 using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Subtitles;
-using MediaBrowser.MediaInfo;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.IO;
@@ -116,10 +115,6 @@ namespace MediaBrowser.Providers.MediaInfo
 
                 cancellationToken.ThrowIfCancellationRequested();
 
-                FFProbeHelpers.NormalizeFFProbeResult(result);
-
-                cancellationToken.ThrowIfCancellationRequested();
-
                 await Fetch(item, cancellationToken, result, isoMount, blurayDiscInfo, options).ConfigureAwait(false);
 
             }
@@ -136,7 +131,7 @@ namespace MediaBrowser.Providers.MediaInfo
 
         private const string SchemaVersion = "1";
 
-        private async Task<InternalMediaInfoResult> GetMediaInfo(Video item,
+        private async Task<Model.Entities.MediaInfo> GetMediaInfo(Video item,
             IIsoMount isoMount,
             CancellationToken cancellationToken)
         {
@@ -149,7 +144,7 @@ namespace MediaBrowser.Providers.MediaInfo
 
             try
             {
-                return _json.DeserializeFromFile<InternalMediaInfoResult>(cachePath);
+                return _json.DeserializeFromFile<Model.Entities.MediaInfo>(cachePath);
             }
             catch (FileNotFoundException)
             {
@@ -165,7 +160,7 @@ namespace MediaBrowser.Providers.MediaInfo
 
             var inputPath = MediaEncoderHelpers.GetInputArgument(item.Path, protocol, isoMount, item.PlayableStreamFileNames);
 
-            var result = await _mediaEncoder.GetMediaInfo(inputPath, protocol, false, cancellationToken).ConfigureAwait(false);
+            var result = await _mediaEncoder.GetMediaInfo(inputPath, item.Path, protocol, false, true, cancellationToken).ConfigureAwait(false);
 
             Directory.CreateDirectory(Path.GetDirectoryName(cachePath));
             _json.SerializeToFile(result, cachePath);
@@ -175,52 +170,37 @@ namespace MediaBrowser.Providers.MediaInfo
 
         protected async Task Fetch(Video video,
             CancellationToken cancellationToken,
-            InternalMediaInfoResult data,
+            Model.Entities.MediaInfo mediaInfo,
             IIsoMount isoMount,
             BlurayDiscInfo blurayInfo,
             MetadataRefreshOptions options)
         {
-            var mediaInfo = MediaEncoderHelpers.GetMediaInfo(data);
             var mediaStreams = mediaInfo.MediaStreams;
 
-            video.TotalBitrate = mediaInfo.TotalBitrate;
-            video.FormatName = (mediaInfo.Format ?? string.Empty)
+            video.TotalBitrate = mediaInfo.Bitrate;
+            video.FormatName = (mediaInfo.Container ?? string.Empty)
                 .Replace("matroska", "mkv", StringComparison.OrdinalIgnoreCase);
 
-            if (data.format != null)
-            {
-                // For dvd's this may not always be accurate, so don't set the runtime if the item already has one
-                var needToSetRuntime = video.VideoType != VideoType.Dvd || video.RunTimeTicks == null || video.RunTimeTicks.Value == 0;
-
-                if (needToSetRuntime && !string.IsNullOrEmpty(data.format.duration))
-                {
-                    video.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(data.format.duration, _usCulture)).Ticks;
-                }
+            // For dvd's this may not always be accurate, so don't set the runtime if the item already has one
+            var needToSetRuntime = video.VideoType != VideoType.Dvd || video.RunTimeTicks == null || video.RunTimeTicks.Value == 0;
 
-                if (video.VideoType == VideoType.VideoFile)
-                {
-                    var extension = (Path.GetExtension(video.Path) ?? string.Empty).TrimStart('.');
+            if (needToSetRuntime)
+            {
+                video.RunTimeTicks = mediaInfo.RunTimeTicks;
+            }
 
-                    video.Container = extension;
-                }
-                else
-                {
-                    video.Container = null;
-                }
+            if (video.VideoType == VideoType.VideoFile)
+            {
+                var extension = (Path.GetExtension(video.Path) ?? string.Empty).TrimStart('.');
 
-                if (!string.IsNullOrEmpty(data.format.size))
-                {
-                    video.Size = long.Parse(data.format.size, _usCulture);
-                }
-                else
-                {
-                    video.Size = null;
-                }
+                video.Container = extension;
+            }
+            else
+            {
+                video.Container = null;
             }
 
-            var mediaChapters = (data.Chapters ?? new MediaChapter[] { }).ToList();
-            var chapters = mediaChapters.Select(GetChapterInfo).ToList();
-
+            var chapters = mediaInfo.Chapters ?? new List<ChapterInfo>();
             if (video.VideoType == VideoType.BluRay || (video.IsoType.HasValue && video.IsoType.Value == IsoType.BluRay))
             {
                 FetchBdInfo(video, chapters, mediaStreams, blurayInfo);
@@ -228,7 +208,7 @@ namespace MediaBrowser.Providers.MediaInfo
 
             await AddExternalSubtitles(video, mediaStreams, options, cancellationToken).ConfigureAwait(false);
 
-            FetchWtvInfo(video, data);
+            FetchEmbeddedInfo(video, mediaInfo);
 
             video.IsHD = mediaStreams.Any(i => i.Type == MediaStreamType.Video && i.Width.HasValue && i.Width.Value >= 1270);
 
@@ -238,9 +218,7 @@ namespace MediaBrowser.Providers.MediaInfo
             video.DefaultVideoStreamIndex = videoStream == null ? (int?)null : videoStream.Index;
 
             video.HasSubtitles = mediaStreams.Any(i => i.Type == MediaStreamType.Subtitle);
-
-            ExtractTimestamp(video);
-            UpdateFromMediaInfo(video, videoStream);
+            video.Timestamp = mediaInfo.Timestamp;
 
             await _itemRepo.SaveMediaStreams(video.Id, mediaStreams, cancellationToken).ConfigureAwait(false);
 
@@ -283,29 +261,6 @@ namespace MediaBrowser.Providers.MediaInfo
             }
         }
 
-        private void UpdateFromMediaInfo(Video video, MediaStream videoStream)
-        {
-            if (video.VideoType == VideoType.VideoFile && video.LocationType != LocationType.Remote && video.LocationType != LocationType.Virtual)
-            {
-                if (videoStream != null)
-                {
-                    try
-                    {
-                        var result = new MediaInfoLib().GetVideoInfo(video.Path);
-
-                        videoStream.IsCabac = result.IsCabac ?? videoStream.IsCabac;
-                        videoStream.IsInterlaced = result.IsInterlaced ?? videoStream.IsInterlaced;
-                        videoStream.BitDepth = result.BitDepth ?? videoStream.BitDepth;
-                        videoStream.RefFrames = result.RefFrames;
-                    }
-                    catch (Exception ex)
-                    {
-                        _logger.ErrorException("Error running MediaInfo on {0}", ex, video.Path);
-                    }
-                }
-            }
-        }
-
         private void NormalizeChapterNames(List<ChapterInfo> chapters)
         {
             var index = 1;
@@ -325,32 +280,6 @@ namespace MediaBrowser.Providers.MediaInfo
             }
         }
 
-        private ChapterInfo GetChapterInfo(MediaChapter chapter)
-        {
-            var info = new ChapterInfo();
-
-            if (chapter.tags != null)
-            {
-                string name;
-                if (chapter.tags.TryGetValue("title", out name))
-                {
-                    info.Name = name;
-                }
-            }
-
-            // Limit accuracy to milliseconds to match xml saving
-            var secondsString = chapter.start_time;
-            double seconds;
-
-            if (double.TryParse(secondsString, NumberStyles.Any, CultureInfo.InvariantCulture, out seconds))
-            {
-                var ms = Math.Round(TimeSpan.FromSeconds(seconds).TotalMilliseconds);
-                info.StartPositionTicks = TimeSpan.FromMilliseconds(ms).Ticks;
-            }
-
-            return info;
-        }
-
         private void FetchBdInfo(BaseItem item, List<ChapterInfo> chapters, List<MediaStream> mediaStreams, BlurayDiscInfo blurayInfo)
         {
             var video = (Video)item;
@@ -419,129 +348,79 @@ namespace MediaBrowser.Providers.MediaInfo
             return _blurayExaminer.GetDiscInfo(path);
         }
 
-        public const int MaxSubtitleDescriptionExtractionLength = 100; // When extracting subtitles, the maximum length to consider (to avoid invalid filenames)
-        
-        private void FetchWtvInfo(Video video, InternalMediaInfoResult data)
+        private void FetchEmbeddedInfo(Video video, Model.Entities.MediaInfo data)
         {
-            if (data.format == null || data.format.tags == null)
-            {
-                return;
-            }
-
-            if (!video.LockedFields.Contains(MetadataFields.Genres))
-            {
-                var genres = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/Genre");
-
-                if (!string.IsNullOrWhiteSpace(genres))
-                {
-                    //genres = FFProbeHelpers.GetDictionaryValue(data.format.tags, "genre");
-                }
-
-                if (!string.IsNullOrWhiteSpace(genres))
-                {
-                    video.Genres = genres.Split(new[] { ';', '/', ',' }, StringSplitOptions.RemoveEmptyEntries)
-                        .Where(i => !string.IsNullOrWhiteSpace(i))
-                        .Select(i => i.Trim())
-                        .ToList();
-                }
-            }
-
             if (!video.LockedFields.Contains(MetadataFields.OfficialRating))
             {
-                var officialRating = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/ParentalRating");
-
-                if (!string.IsNullOrWhiteSpace(officialRating))
+                if (!string.IsNullOrWhiteSpace(data.OfficialRating))
                 {
-                    video.OfficialRating = officialRating;
+                    video.OfficialRating = data.OfficialRating;
                 }
             }
 
             if (!video.LockedFields.Contains(MetadataFields.Cast))
             {
-                var people = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/MediaCredits");
+                video.People.Clear();
 
-                if (!string.IsNullOrEmpty(people))
+                foreach (var person in data.People)
                 {
-                    video.People = people.Split(new[] { ';', '/' }, StringSplitOptions.RemoveEmptyEntries)
-                        .Where(i => !string.IsNullOrWhiteSpace(i))
-                        .Select(i => new PersonInfo { Name = i.Trim(), Type = PersonType.Actor })
-                        .ToList();
+                    video.AddPerson(new PersonInfo
+                    {
+                        Name = person.Name,
+                        Type = person.Type,
+                        Role = person.Role
+                    });
                 }
             }
 
-            var year = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/OriginalReleaseTime");
-            if (!string.IsNullOrWhiteSpace(year))
+            if (!video.LockedFields.Contains(MetadataFields.Genres))
             {
-                int val;
+                video.Genres.Clear();
 
-                if (int.TryParse(year, NumberStyles.Integer, _usCulture, out val))
+                foreach (var genre in data.Genres)
                 {
-                    video.ProductionYear = val;
+                    video.AddGenre(genre);
                 }
             }
 
-            var premiereDateString = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/MediaOriginalBroadcastDateTime");
-            if (!string.IsNullOrWhiteSpace(premiereDateString))
+            if (!video.LockedFields.Contains(MetadataFields.Studios))
             {
-                DateTime val;
+                video.Studios.Clear();
 
-                // Credit to MCEBuddy: https://mcebuddy2x.codeplex.com/
-                // DateTime is reported along with timezone info (typically Z i.e. UTC hence assume None)
-                if (DateTime.TryParse(year, null, DateTimeStyles.None, out val))
+                foreach (var studio in data.Studios)
                 {
-                    video.PremiereDate = val.ToUniversalTime();
+                    video.AddStudio(studio);
                 }
             }
 
-            var description = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/SubTitleDescription");
-            
-            var episode = video as Episode;
-            if (episode != null)
+            if (data.ProductionYear.HasValue)
             {
-                var subTitle = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/SubTitle");
-
-                // For below code, credit to MCEBuddy: https://mcebuddy2x.codeplex.com/
-
-                // Sometimes for TV Shows the Subtitle field is empty and the subtitle description contains the subtitle, extract if possible. See ticket https://mcebuddy2x.codeplex.com/workitem/1910
-                // The format is -> EPISODE/TOTAL_EPISODES_IN_SEASON. SUBTITLE: DESCRIPTION
-                // OR -> COMMENT. SUBTITLE: DESCRIPTION
-                // e.g. -> 4/13. The Doctor's Wife: Science fiction drama. When he follows a Time Lord distress signal, the Doctor puts Amy, Rory and his beloved TARDIS in grave danger. Also in HD. [AD,S]
-                // e.g. -> CBeebies Bedtime Hour. The Mystery: Animated adventures of two friends who live on an island in the middle of the big city. Some of Abney and Teal's favourite objects are missing. [S]
-                if (String.IsNullOrWhiteSpace(subTitle) && !String.IsNullOrWhiteSpace(description) && description.Substring(0, Math.Min(description.Length, MaxSubtitleDescriptionExtractionLength)).Contains(":")) // Check within the Subtitle size limit, otherwise from description it can get too long creating an invalid filename
-                {
-                    string[] parts = description.Split(':');
-                    if (parts.Length > 0)
-                    {
-                        string subtitle = parts[0];
-                        try
-                        {
-                            if (subtitle.Contains("/")) // It contains a episode number and season number
-                            {
-                                string[] numbers = subtitle.Split(' ');
-                                episode.IndexNumber = int.Parse(numbers[0].Replace(".", "").Split('/')[0]);
-                                int totalEpisodesInSeason = int.Parse(numbers[0].Replace(".", "").Split('/')[1]);
+                video.ProductionYear = data.ProductionYear;
+            }
+            if (data.PremiereDate.HasValue)
+            {
+                video.PremiereDate = data.PremiereDate;
+            }
+            if (data.IndexNumber.HasValue)
+            {
+                video.IndexNumber = data.IndexNumber;
+            }
+            if (data.ParentIndexNumber.HasValue)
+            {
+                video.ParentIndexNumber = data.ParentIndexNumber;
+            }
 
-                                description = String.Join(" ", numbers, 1, numbers.Length - 1).Trim(); // Skip the first, concatenate the rest, clean up spaces and save it
-                            }
-                            else
-                                throw new Exception(); // Switch to default parsing
-                        }
-                        catch // Default parsing
-                        {
-                            if (subtitle.Contains(".")) // skip the comment, keep the subtitle
-                                description = String.Join(".", subtitle.Split('.'), 1, subtitle.Split('.').Length - 1).Trim(); // skip the first
-                            else
-                                description = subtitle.Trim(); // Clean up whitespaces and save it
-                        }
-                    }
-                }
+            // If we don't have a ProductionYear try and get it from PremiereDate
+            if (video.PremiereDate.HasValue && !video.ProductionYear.HasValue)
+            {
+                video.ProductionYear = video.PremiereDate.Value.ToLocalTime().Year;
             }
 
             if (!video.LockedFields.Contains(MetadataFields.Overview))
             {
-                if (!string.IsNullOrWhiteSpace(description))
+                if (!string.IsNullOrWhiteSpace(data.Overview))
                 {
-                    video.Overview = description;
+                    video.Overview = data.Overview;
                 }
             }
         }
@@ -709,56 +588,6 @@ namespace MediaBrowser.Providers.MediaInfo
             }
         }
 
-        private void ExtractTimestamp(Video video)
-        {
-            if (video.VideoType == VideoType.VideoFile)
-            {
-                if (string.Equals(video.Container, "mpeg2ts", StringComparison.OrdinalIgnoreCase) ||
-                    string.Equals(video.Container, "m2ts", StringComparison.OrdinalIgnoreCase) ||
-                    string.Equals(video.Container, "ts", StringComparison.OrdinalIgnoreCase))
-                {
-                    try
-                    {
-                        video.Timestamp = GetMpegTimestamp(video.Path);
-
-                        _logger.Debug("Video has {0} timestamp", video.Timestamp);
-                    }
-                    catch (Exception ex)
-                    {
-                        _logger.ErrorException("Error extracting timestamp info from {0}", ex, video.Path);
-                        video.Timestamp = null;
-                    }
-                }
-            }
-        }
-
-        private TransportStreamTimestamp GetMpegTimestamp(string path)
-        {
-            var packetBuffer = new byte['Å'];
-
-            using (var fs = _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
-            {
-                fs.Read(packetBuffer, 0, packetBuffer.Length);
-            }
-
-            if (packetBuffer[0] == 71)
-            {
-                return TransportStreamTimestamp.None;
-            }
-
-            if ((packetBuffer[4] == 71) && (packetBuffer['Ä'] == 71))
-            {
-                if ((packetBuffer[0] == 0) && (packetBuffer[1] == 0) && (packetBuffer[2] == 0) && (packetBuffer[3] == 0))
-                {
-                    return TransportStreamTimestamp.Zero;
-                }
-
-                return TransportStreamTimestamp.Valid;
-            }
-
-            return TransportStreamTimestamp.None;
-        }
-
         private void FetchFromDvdLib(Video item, IIsoMount mount)
         {
             var path = mount == null ? item.Path : mount.MountedPath;

+ 78 - 3
MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs

@@ -1,6 +1,7 @@
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Controller.MediaEncoding;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Serialization;
@@ -18,12 +19,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv
         private readonly IJsonSerializer _jsonSerializer;
         private readonly ILogger _logger;
         private readonly IMediaSourceManager _mediaSourceManager;
+        private readonly IMediaEncoder _mediaEncoder;
 
-        public LiveTvMediaSourceProvider(ILiveTvManager liveTvManager, IJsonSerializer jsonSerializer, ILogManager logManager, IMediaSourceManager mediaSourceManager)
+        public LiveTvMediaSourceProvider(ILiveTvManager liveTvManager, IJsonSerializer jsonSerializer, ILogManager logManager, IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder)
         {
             _liveTvManager = liveTvManager;
             _jsonSerializer = jsonSerializer;
             _mediaSourceManager = mediaSourceManager;
+            _mediaEncoder = mediaEncoder;
             _logger = logManager.GetLogger(GetType().Name);
         }
 
@@ -90,14 +93,86 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
         public async Task<MediaSourceInfo> OpenMediaSource(string openToken, CancellationToken cancellationToken)
         {
+            MediaSourceInfo stream;
+            var isAudio = false;
+
             var keys = openToken.Split(new[] { '|' }, 2);
 
             if (string.Equals(keys[0], typeof(LiveTvChannel).Name, StringComparison.OrdinalIgnoreCase))
             {
-                return await _liveTvManager.GetChannelStream(keys[1], cancellationToken).ConfigureAwait(false);
+                stream = await _liveTvManager.GetChannelStream(keys[1], cancellationToken).ConfigureAwait(false);
+            }
+            else
+            {
+                stream = await _liveTvManager.GetRecordingStream(keys[1], cancellationToken).ConfigureAwait(false);
+            }
+
+            try
+            {
+                await AddMediaInfo(stream, isAudio, cancellationToken).ConfigureAwait(false);
+            }
+            catch (Exception ex)
+            {
+                _logger.ErrorException("Error probing live tv stream", ex);
+            }
+
+            return stream;
+        }
+
+        private async Task AddMediaInfo(MediaSourceInfo mediaSource, bool isAudio, CancellationToken cancellationToken)
+        {
+            var inputPaths = new[] { mediaSource.Path };
+
+            var info = await _mediaEncoder.GetMediaInfo(inputPaths, mediaSource.Path, mediaSource.Protocol, isAudio, false, cancellationToken)
+                        .ConfigureAwait(false);
+
+            mediaSource.Bitrate = info.Bitrate;
+            mediaSource.Container = info.Container;
+            mediaSource.Formats = info.Formats;
+            mediaSource.MediaStreams = info.MediaStreams;
+            mediaSource.RunTimeTicks = info.RunTimeTicks;
+            mediaSource.Size = info.Size;
+            mediaSource.Timestamp = info.Timestamp;
+            mediaSource.Video3DFormat = info.Video3DFormat;
+            mediaSource.VideoType = info.VideoType;
+
+            mediaSource.DefaultSubtitleStreamIndex = null;
+
+            var audioStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == Model.Entities.MediaStreamType.Audio);
+
+            if (audioStream == null || audioStream.Index == -1)
+            {
+                mediaSource.DefaultAudioStreamIndex = null;
+            }
+            else
+            {
+                mediaSource.DefaultAudioStreamIndex = audioStream.Index;
             }
 
-            return await _liveTvManager.GetRecordingStream(keys[1], cancellationToken).ConfigureAwait(false);
+            // Try to estimate this
+            if (!mediaSource.Bitrate.HasValue)
+            {
+                var videoStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == Model.Entities.MediaStreamType.Video);
+                if (videoStream != null)
+                {
+                    var width = videoStream.Width ?? 1920;
+
+                    if (width >= 1900)
+                    {
+                        mediaSource.Bitrate = 10000000;
+                    }
+
+                    else if (width >= 1260)
+                    {
+                        mediaSource.Bitrate = 6000000;
+                    }
+
+                    else if (width >= 700)
+                    {
+                        mediaSource.Bitrate = 4000000;
+                    }
+                }
+            }
         }
 
         public Task CloseMediaSource(string liveStreamId, CancellationToken cancellationToken)

+ 2 - 2
MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs

@@ -493,7 +493,7 @@ namespace MediaBrowser.Server.Implementations.Sync
             conversionOptions.ItemId = item.Id.ToString("N");
             conversionOptions.MediaSources = _mediaSourceManager.GetStaticMediaSources(item, false, user).ToList();
 
-            var streamInfo = new StreamBuilder().BuildVideoItem(conversionOptions);
+            var streamInfo = new StreamBuilder(_logger).BuildVideoItem(conversionOptions);
             var mediaSource = streamInfo.MediaSource;
 
             // No sense creating external subs if we're already burning one into the video
@@ -690,7 +690,7 @@ namespace MediaBrowser.Server.Implementations.Sync
             conversionOptions.ItemId = item.Id.ToString("N");
             conversionOptions.MediaSources = _mediaSourceManager.GetStaticMediaSources(item, false, user).ToList();
 
-            var streamInfo = new StreamBuilder().BuildAudioItem(conversionOptions);
+            var streamInfo = new StreamBuilder(_logger).BuildAudioItem(conversionOptions);
             var mediaSource = streamInfo.MediaSource;
 
             jobItem.MediaSourceId = streamInfo.MediaSourceId;

+ 2 - 2
Nuget/MediaBrowser.Common.Internal.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
     <metadata>
         <id>MediaBrowser.Common.Internal</id>
-        <version>3.0.613</version>
+        <version>3.0.615</version>
         <title>MediaBrowser.Common.Internal</title>
         <authors>Luke</authors>
         <owners>ebr,Luke,scottisafool</owners>
@@ -12,7 +12,7 @@
         <description>Contains common components shared by Emby Theater and Emby Server. Not intended for plugin developer consumption.</description>
         <copyright>Copyright © Emby 2013</copyright>
         <dependencies>
-            <dependency id="MediaBrowser.Common" version="3.0.613" />
+            <dependency id="MediaBrowser.Common" version="3.0.615" />
             <dependency id="NLog" version="3.2.0.0" />
             <dependency id="SimpleInjector" version="2.7.0" />
         </dependencies>

+ 1 - 1
Nuget/MediaBrowser.Common.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
     <metadata>
         <id>MediaBrowser.Common</id>
-        <version>3.0.613</version>
+        <version>3.0.615</version>
         <title>MediaBrowser.Common</title>
         <authors>Emby Team</authors>
         <owners>ebr,Luke,scottisafool</owners>

+ 1 - 1
Nuget/MediaBrowser.Model.Signed.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
     <metadata>
         <id>MediaBrowser.Model.Signed</id>
-        <version>3.0.613</version>
+        <version>3.0.615</version>
         <title>MediaBrowser.Model - Signed Edition</title>
         <authors>Emby Team</authors>
         <owners>ebr,Luke,scottisafool</owners>

+ 2 - 2
Nuget/MediaBrowser.Server.Core.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
     <metadata>
         <id>MediaBrowser.Server.Core</id>
-        <version>3.0.613</version>
+        <version>3.0.615</version>
         <title>Media Browser.Server.Core</title>
         <authors>Emby Team</authors>
         <owners>ebr,Luke,scottisafool</owners>
@@ -12,7 +12,7 @@
         <description>Contains core components required to build plugins for Emby Server.</description>
         <copyright>Copyright © Emby 2013</copyright>
         <dependencies>
-            <dependency id="MediaBrowser.Common" version="3.0.613" />
+            <dependency id="MediaBrowser.Common" version="3.0.615" />
         </dependencies>
     </metadata>
     <files>

+ 2 - 2
SharedVersion.cs

@@ -1,4 +1,4 @@
 using System.Reflection;
 
-//[assembly: AssemblyVersion("3.0.*")]
-[assembly: AssemblyVersion("3.0.5572.0")]
+[assembly: AssemblyVersion("3.0.*")]
+//[assembly: AssemblyVersion("3.0.5572.0")]