Browse Source

improve direct play of live streams

Luke Pulverenti 10 năm trước cách đây
mục cha
commit
2b7a80cfb5
25 tập tin đã thay đổi với 1200 bổ sung878 xóa
  1. 1 1
      MediaBrowser.Api/Playback/MediaInfoService.cs
  2. 0 1
      MediaBrowser.Controller/MediaBrowser.Controller.csproj
  3. 4 2
      MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
  4. 1 289
      MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs
  5. 3 2
      MediaBrowser.Dlna/Didl/DidlBuilder.cs
  6. 2 2
      MediaBrowser.Dlna/PlayTo/PlayToController.cs
  7. 13 6
      MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
  8. 7 0
      MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
  9. 1 1
      MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs
  10. 3 3
      MediaBrowser.MediaEncoding/Probing/InternalMediaInfoResult.cs
  11. 882 0
      MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
  12. 40 3
      MediaBrowser.Model/Dlna/StreamBuilder.cs
  13. 1 0
      MediaBrowser.Model/Dlna/StreamInfo.cs
  14. 2 2
      MediaBrowser.Model/Dto/BaseItemPerson.cs
  15. 50 11
      MediaBrowser.Model/Entities/MediaInfo.cs
  16. 0 5
      MediaBrowser.Providers/MediaBrowser.Providers.csproj
  17. 39 303
      MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs
  18. 63 234
      MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
  19. 78 3
      MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs
  20. 2 2
      MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs
  21. 2 2
      Nuget/MediaBrowser.Common.Internal.nuspec
  22. 1 1
      Nuget/MediaBrowser.Common.nuspec
  23. 1 1
      Nuget/MediaBrowser.Model.Signed.nuspec
  24. 2 2
      Nuget/MediaBrowser.Server.Core.nuspec
  25. 2 2
      SharedVersion.cs

+ 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")]