Browse Source

moved media streams to the database

Luke Pulverenti 11 years ago
parent
commit
5f0d8000a5
39 changed files with 653 additions and 328 deletions
  1. 0 38
      MediaBrowser.Api/LibraryService.cs
  2. 16 6
      MediaBrowser.Api/Playback/BaseStreamingService.cs
  3. 3 10
      MediaBrowser.Api/Playback/Hls/AudioHlsService.cs
  4. 7 14
      MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
  5. 9 17
      MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
  6. 1 4
      MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
  7. 3 9
      MediaBrowser.Api/UserLibrary/ItemsService.cs
  8. 6 8
      MediaBrowser.Controller/Entities/Audio/Audio.cs
  9. 13 2
      MediaBrowser.Controller/Entities/Book.cs
  10. 10 0
      MediaBrowser.Controller/Entities/IHasMediaStreams.cs
  11. 5 0
      MediaBrowser.Controller/Entities/IHasScreenshots.cs
  12. 19 18
      MediaBrowser.Controller/Entities/Video.cs
  13. 2 0
      MediaBrowser.Controller/MediaBrowser.Controller.csproj
  14. 14 4
      MediaBrowser.Controller/MediaInfo/FFMpegManager.cs
  15. 16 0
      MediaBrowser.Controller/Persistence/IItemRepository.cs
  16. 26 0
      MediaBrowser.Controller/Persistence/MediaStreamQuery.cs
  17. 1 1
      MediaBrowser.Controller/Resolvers/EntityResolutionHelper.cs
  18. 0 3
      MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
  19. 0 3
      MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
  20. 0 3
      MediaBrowser.Model/Dto/ItemCounts.cs
  21. 0 26
      MediaBrowser.Model/Entities/IHasMediaStreams.cs
  22. 3 3
      MediaBrowser.Model/Entities/MediaStream.cs
  23. 0 1
      MediaBrowser.Model/MediaBrowser.Model.csproj
  24. 6 1
      MediaBrowser.Providers/ImageFromMediaLocationProvider.cs
  25. 1 1
      MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs
  26. 14 11
      MediaBrowser.Providers/MediaInfo/FFProbeAudioInfoProvider.cs
  27. 35 21
      MediaBrowser.Providers/MediaInfo/FFProbeVideoInfoProvider.cs
  28. 1 1
      MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs
  29. 24 93
      MediaBrowser.Providers/Savers/XmlSaverHelpers.cs
  30. 2 2
      MediaBrowser.Server.Implementations/BdInfo/BdInfoExaminer.cs
  31. 5 1
      MediaBrowser.Server.Implementations/Dto/DtoService.cs
  32. 1 0
      MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
  33. 24 0
      MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs
  34. 377 0
      MediaBrowser.Server.Implementations/Persistence/SqliteMediaStreamsRepository.cs
  35. 4 14
      MediaBrowser.Server.Implementations/Persistence/SqliteUserDataRepository.cs
  36. 1 1
      MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs
  37. 2 10
      MediaBrowser.Server.Implementations/Sorting/VideoBitRateComparer.cs
  38. 1 1
      MediaBrowser.ServerApplication/ApplicationHost.cs
  39. 1 1
      MediaBrowser.ServerApplication/FFMpeg/FFMpegDownloadInfo.cs

+ 0 - 38
MediaBrowser.Api/LibraryService.cs

@@ -370,44 +370,6 @@ namespace MediaBrowser.Api
                 UniqueTypes = items.Select(i => i.GetClientTypeName()).Distinct().ToList()
                 UniqueTypes = items.Select(i => i.GetClientTypeName()).Distinct().ToList()
             };
             };
 
 
-            var people = items.SelectMany(i => i.People)
-             .Select(i => i.Name)
-             .Distinct(StringComparer.OrdinalIgnoreCase)
-             .Select(i =>
-             {
-                 try
-                 {
-                     return _libraryManager.GetPerson(i);
-                 }
-                 catch
-                 {
-                     return null;
-                 }
-             })
-             .Where(i => i != null)
-             .ToList();
-
-            people = request.UserId.HasValue ? FilterItems(people, request, request.UserId.Value).ToList() : people;
-            counts.PersonCount = people.Count;
-
-            var artists = _libraryManager.GetAllArtists(items)
-                .Select(i =>
-                {
-                    try
-                    {
-                        return _libraryManager.GetArtist(i);
-                    }
-                    catch
-                    {
-                        return null;
-                    }
-                })
-                .Where(i => i != null)
-                .ToList();
-
-            artists = request.UserId.HasValue ? FilterItems(artists, request, request.UserId.Value).ToList() : artists;
-            counts.ArtistCount = artists.Count;
-
             return ToOptimizedResult(counts);
             return ToOptimizedResult(counts);
         }
         }
 
 

+ 16 - 6
MediaBrowser.Api/Playback/BaseStreamingService.cs

@@ -7,6 +7,7 @@ using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.MediaInfo;
 using MediaBrowser.Controller.MediaInfo;
+using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Model.Drawing;
 using MediaBrowser.Model.Drawing;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
@@ -59,7 +60,9 @@ namespace MediaBrowser.Api.Playback
         protected IDtoService DtoService { get; private set; }
         protected IDtoService DtoService { get; private set; }
 
 
         protected IFileSystem FileSystem { get; private set; }
         protected IFileSystem FileSystem { get; private set; }
-        
+
+        protected IItemRepository ItemRepository { get; private set; }
+
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="BaseStreamingService" /> class.
         /// Initializes a new instance of the <see cref="BaseStreamingService" /> class.
         /// </summary>
         /// </summary>
@@ -68,8 +71,9 @@ namespace MediaBrowser.Api.Playback
         /// <param name="libraryManager">The library manager.</param>
         /// <param name="libraryManager">The library manager.</param>
         /// <param name="isoManager">The iso manager.</param>
         /// <param name="isoManager">The iso manager.</param>
         /// <param name="mediaEncoder">The media encoder.</param>
         /// <param name="mediaEncoder">The media encoder.</param>
-        protected BaseStreamingService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem)
+        protected BaseStreamingService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository)
         {
         {
+            ItemRepository = itemRepository;
             FileSystem = fileSystem;
             FileSystem = fileSystem;
             DtoService = dtoService;
             DtoService = dtoService;
             ApplicationPaths = appPaths;
             ApplicationPaths = appPaths;
@@ -803,6 +807,12 @@ namespace MediaBrowser.Api.Playback
 
 
             var videoRequest = request as VideoStreamRequest;
             var videoRequest = request as VideoStreamRequest;
 
 
+            var mediaStreams = ItemRepository.GetMediaStreams(new MediaStreamQuery
+            {
+                ItemId = item.Id
+
+            }).ToList();
+
             if (videoRequest != null)
             if (videoRequest != null)
             {
             {
                 if (!videoRequest.VideoCodec.HasValue)
                 if (!videoRequest.VideoCodec.HasValue)
@@ -810,13 +820,13 @@ namespace MediaBrowser.Api.Playback
                     videoRequest.VideoCodec = InferVideoCodec(url);
                     videoRequest.VideoCodec = InferVideoCodec(url);
                 }
                 }
 
 
-                state.VideoStream = GetMediaStream(media.MediaStreams, videoRequest.VideoStreamIndex, MediaStreamType.Video);
-                state.SubtitleStream = GetMediaStream(media.MediaStreams, videoRequest.SubtitleStreamIndex, MediaStreamType.Subtitle, false);
-                state.AudioStream = GetMediaStream(media.MediaStreams, videoRequest.AudioStreamIndex, MediaStreamType.Audio);
+                state.VideoStream = GetMediaStream(mediaStreams, videoRequest.VideoStreamIndex, MediaStreamType.Video);
+                state.SubtitleStream = GetMediaStream(mediaStreams, videoRequest.SubtitleStreamIndex, MediaStreamType.Subtitle, false);
+                state.AudioStream = GetMediaStream(mediaStreams, videoRequest.AudioStreamIndex, MediaStreamType.Audio);
             }
             }
             else
             else
             {
             {
-                state.AudioStream = GetMediaStream(media.MediaStreams, null, MediaStreamType.Audio, true);
+                state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true);
             }
             }
 
 
             return state;
             return state;

+ 3 - 10
MediaBrowser.Api/Playback/Hls/AudioHlsService.cs

@@ -3,6 +3,7 @@ using MediaBrowser.Common.MediaInfo;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.IO;
 using ServiceStack.ServiceHost;
 using ServiceStack.ServiceHost;
@@ -25,16 +26,8 @@ namespace MediaBrowser.Api.Playback.Hls
     /// </summary>
     /// </summary>
     public class AudioHlsService : BaseHlsService
     public class AudioHlsService : BaseHlsService
     {
     {
-        /// <summary>
-        /// Initializes a new instance of the <see cref="AudioHlsService" /> class.
-        /// </summary>
-        /// <param name="appPaths">The app paths.</param>
-        /// <param name="userManager">The user manager.</param>
-        /// <param name="libraryManager">The library manager.</param>
-        /// <param name="isoManager">The iso manager.</param>
-        /// <param name="mediaEncoder">The media encoder.</param>
-        public AudioHlsService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem)
-            : base(appPaths, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem)
+        public AudioHlsService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository)
+            : base(appPaths, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository)
         {
         {
         }
         }
 
 

+ 7 - 14
MediaBrowser.Api/Playback/Hls/BaseHlsService.cs

@@ -6,6 +6,7 @@ using MediaBrowser.Controller;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.IO;
 using System;
 using System;
@@ -21,6 +22,11 @@ namespace MediaBrowser.Api.Playback.Hls
     /// </summary>
     /// </summary>
     public abstract class BaseHlsService : BaseStreamingService
     public abstract class BaseHlsService : BaseStreamingService
     {
     {
+        protected BaseHlsService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository)
+            : base(appPaths, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository)
+        {
+        }
+
         protected override string GetOutputFilePath(StreamState state)
         protected override string GetOutputFilePath(StreamState state)
         {
         {
             var folder = ApplicationPaths.EncodedMediaCachePath;
             var folder = ApplicationPaths.EncodedMediaCachePath;
@@ -30,19 +36,6 @@ namespace MediaBrowser.Api.Playback.Hls
             return Path.Combine(folder, GetCommandLineArguments("dummy\\dummy", state, false).GetMD5() + (outputFileExtension ?? string.Empty).ToLower());
             return Path.Combine(folder, GetCommandLineArguments("dummy\\dummy", state, false).GetMD5() + (outputFileExtension ?? string.Empty).ToLower());
         }
         }
 
 
-        /// <summary>
-        /// Initializes a new instance of the <see cref="BaseStreamingService" /> class.
-        /// </summary>
-        /// <param name="appPaths">The app paths.</param>
-        /// <param name="userManager">The user manager.</param>
-        /// <param name="libraryManager">The library manager.</param>
-        /// <param name="isoManager">The iso manager.</param>
-        /// <param name="mediaEncoder">The media encoder.</param>
-        protected BaseHlsService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem)
-            : base(appPaths, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem)
-        {
-        }
-
         /// <summary>
         /// <summary>
         /// Gets the audio arguments.
         /// Gets the audio arguments.
         /// </summary>
         /// </summary>
@@ -260,7 +253,7 @@ namespace MediaBrowser.Api.Playback.Hls
 
 
             var itsOffsetMs = hlsVideoRequest == null
             var itsOffsetMs = hlsVideoRequest == null
                                        ? 0
                                        ? 0
-                                       : ((GetHlsVideoStream) state.VideoRequest).TimeStampOffsetMs;
+                                       : ((GetHlsVideoStream)state.VideoRequest).TimeStampOffsetMs;
 
 
             var itsOffset = itsOffsetMs == 0 ? string.Empty : string.Format("-itsoffset {0} ", TimeSpan.FromMilliseconds(itsOffsetMs).TotalSeconds);
             var itsOffset = itsOffsetMs == 0 ? string.Empty : string.Format("-itsoffset {0} ", TimeSpan.FromMilliseconds(itsOffsetMs).TotalSeconds);
 
 

+ 9 - 17
MediaBrowser.Api/Playback/Hls/VideoHlsService.cs

@@ -3,6 +3,7 @@ using MediaBrowser.Common.MediaInfo;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.IO;
 using ServiceStack.ServiceHost;
 using ServiceStack.ServiceHost;
 using System;
 using System;
@@ -31,17 +32,8 @@ namespace MediaBrowser.Api.Playback.Hls
     /// </summary>
     /// </summary>
     public class VideoHlsService : BaseHlsService
     public class VideoHlsService : BaseHlsService
     {
     {
-        /// <summary>
-        /// Initializes a new instance of the <see cref="BaseStreamingService" /> class.
-        /// </summary>
-        /// <param name="appPaths">The app paths.</param>
-        /// <param name="userManager">The user manager.</param>
-        /// <param name="libraryManager">The library manager.</param>
-        /// <param name="isoManager">The iso manager.</param>
-        /// <param name="mediaEncoder">The media encoder.</param>
-        /// <param name="dtoService">The dto service.</param>
-        public VideoHlsService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem)
-            : base(appPaths, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem)
+        public VideoHlsService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository)
+            : base(appPaths, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository)
         {
         {
         }
         }
 
 
@@ -95,13 +87,13 @@ namespace MediaBrowser.Api.Playback.Hls
                 {
                 {
                     volParam = ",volume=2.000000";
                     volParam = ",volume=2.000000";
                 }
                 }
-                
+
                 if (state.Request.AudioSampleRate.HasValue)
                 if (state.Request.AudioSampleRate.HasValue)
                 {
                 {
-                    audioSampleRate= state.Request.AudioSampleRate.Value + ":";
+                    audioSampleRate = state.Request.AudioSampleRate.Value + ":";
                 }
                 }
 
 
-                args += string.Format(" -af \"adelay=1,aresample={0}async=1000{1}\"",audioSampleRate, volParam);
+                args += string.Format(" -af \"adelay=1,aresample={0}async=1000{1}\"", audioSampleRate, volParam);
 
 
                 return args;
                 return args;
             }
             }
@@ -130,7 +122,7 @@ namespace MediaBrowser.Api.Playback.Hls
             var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsExternal &&
             var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsExternal &&
                                  (state.SubtitleStream.Codec.IndexOf("pgs", StringComparison.OrdinalIgnoreCase) != -1 ||
                                  (state.SubtitleStream.Codec.IndexOf("pgs", StringComparison.OrdinalIgnoreCase) != -1 ||
                                   state.SubtitleStream.Codec.IndexOf("dvd", StringComparison.OrdinalIgnoreCase) != -1);
                                   state.SubtitleStream.Codec.IndexOf("dvd", StringComparison.OrdinalIgnoreCase) != -1);
-            
+
             var args = "-codec:v:0 " + codec + " -preset superfast" + keyFrameArg;
             var args = "-codec:v:0 " + codec + " -preset superfast" + keyFrameArg;
 
 
             var bitrate = GetVideoBitrateParam(state);
             var bitrate = GetVideoBitrateParam(state);
@@ -139,7 +131,7 @@ namespace MediaBrowser.Api.Playback.Hls
             {
             {
                 args += string.Format(" -b:v {0} -maxrate ({0}*.85) -bufsize {0}", bitrate.Value.ToString(UsCulture));
                 args += string.Format(" -b:v {0} -maxrate ({0}*.85) -bufsize {0}", bitrate.Value.ToString(UsCulture));
             }
             }
-            
+
             // Add resolution params, if specified
             // Add resolution params, if specified
             if (!hasGraphicalSubs)
             if (!hasGraphicalSubs)
             {
             {
@@ -171,7 +163,7 @@ namespace MediaBrowser.Api.Playback.Hls
             {
             {
                 args += GetInternalGraphicalSubtitleParam(state, codec);
                 args += GetInternalGraphicalSubtitleParam(state, codec);
             }
             }
-         
+
             return args;
             return args;
         }
         }
 
 

+ 1 - 4
MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs

@@ -12,7 +12,6 @@ using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.IO;
-using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.IO;
 using System.IO;
 using System.Net.Http;
 using System.Net.Http;
@@ -25,13 +24,11 @@ namespace MediaBrowser.Api.Playback.Progressive
     /// </summary>
     /// </summary>
     public abstract class BaseProgressiveStreamingService : BaseStreamingService
     public abstract class BaseProgressiveStreamingService : BaseStreamingService
     {
     {
-        protected readonly IItemRepository ItemRepository;
         protected readonly IImageProcessor ImageProcessor;
         protected readonly IImageProcessor ImageProcessor;
 
 
         protected BaseProgressiveStreamingService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IItemRepository itemRepository, IDtoService dtoService, IImageProcessor imageProcessor, IFileSystem fileSystem) :
         protected BaseProgressiveStreamingService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IItemRepository itemRepository, IDtoService dtoService, IImageProcessor imageProcessor, IFileSystem fileSystem) :
-            base(appPaths, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem)
+            base(appPaths, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository)
         {
         {
-            ItemRepository = itemRepository;
             ImageProcessor = imageProcessor;
             ImageProcessor = imageProcessor;
         }
         }
 
 

+ 3 - 9
MediaBrowser.Api/UserLibrary/ItemsService.cs

@@ -226,7 +226,7 @@ namespace MediaBrowser.Api.UserLibrary
 
 
         [ApiMember(Name = "HasTvdbId", Description = "Optional filter by items that have a tvdb id or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
         [ApiMember(Name = "HasTvdbId", Description = "Optional filter by items that have a tvdb id or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
         public bool? HasTvdbId { get; set; }
         public bool? HasTvdbId { get; set; }
-        
+
         [ApiMember(Name = "IsYearMismatched", Description = "Optional filter by items that are potentially misidentified.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
         [ApiMember(Name = "IsYearMismatched", Description = "Optional filter by items that are potentially misidentified.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
         public bool? IsYearMismatched { get; set; }
         public bool? IsYearMismatched { get; set; }
     }
     }
@@ -984,15 +984,9 @@ namespace MediaBrowser.Api.UserLibrary
 
 
             if (request.HasSubtitles.HasValue)
             if (request.HasSubtitles.HasValue)
             {
             {
-                items = items.OfType<Video>().Where(i =>
-                {
-                    if (request.HasSubtitles.Value)
-                    {
-                        return i.MediaStreams != null && i.MediaStreams.Any(m => m.Type == MediaStreamType.Subtitle);
-                    }
+                var val = request.HasSubtitles.Value;
 
 
-                    return i.MediaStreams == null || i.MediaStreams.All(m => m.Type != MediaStreamType.Subtitle);
-                });
+                items = items.OfType<Video>().Where(i => val == i.HasSubtitles);
             }
             }
 
 
             if (request.HasParentalRating.HasValue)
             if (request.HasParentalRating.HasValue)

+ 6 - 8
MediaBrowser.Controller/Entities/Audio/Audio.cs

@@ -1,5 +1,4 @@
-using MediaBrowser.Model.Entities;
-using System;
+using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
 using System.Runtime.Serialization;
 using System.Runtime.Serialization;
@@ -13,7 +12,6 @@ namespace MediaBrowser.Controller.Entities.Audio
     {
     {
         public Audio()
         public Audio()
         {
         {
-            MediaStreams = new List<MediaStream>();
             Artists = new List<string>();
             Artists = new List<string>();
         }
         }
 
 
@@ -22,13 +20,13 @@ namespace MediaBrowser.Controller.Entities.Audio
         /// </summary>
         /// </summary>
         /// <value>The language.</value>
         /// <value>The language.</value>
         public string Language { get; set; }
         public string Language { get; set; }
-        
+
         /// <summary>
         /// <summary>
-        /// Gets or sets the media streams.
+        /// Gets or sets a value indicating whether this instance has embedded image.
         /// </summary>
         /// </summary>
-        /// <value>The media streams.</value>
-        public List<MediaStream> MediaStreams { get; set; }
-
+        /// <value><c>true</c> if this instance has embedded image; otherwise, <c>false</c>.</value>
+        public bool HasEmbeddedImage { get; set; }
+        
         /// <summary>
         /// <summary>
         /// Override this to true if class should be grouped under a container in indicies
         /// Override this to true if class should be grouped under a container in indicies
         /// The container class should be defined via IndexContainer
         /// The container class should be defined via IndexContainer

+ 13 - 2
MediaBrowser.Controller/Entities/Book.cs

@@ -1,7 +1,8 @@
-
+using System.Collections.Generic;
+
 namespace MediaBrowser.Controller.Entities
 namespace MediaBrowser.Controller.Entities
 {
 {
-    public class Book : BaseItem
+    public class Book : BaseItem, IHasTags
     {
     {
         public override string MediaType
         public override string MediaType
         {
         {
@@ -10,6 +11,11 @@ namespace MediaBrowser.Controller.Entities
                 return Model.Entities.MediaType.Book;
                 return Model.Entities.MediaType.Book;
             }
             }
         }
         }
+        /// <summary>
+        /// Gets or sets the tags.
+        /// </summary>
+        /// <value>The tags.</value>
+        public List<string> Tags { get; set; }
 
 
         public string SeriesName { get; set; }
         public string SeriesName { get; set; }
 
 
@@ -31,5 +37,10 @@ namespace MediaBrowser.Controller.Entities
                 return !IsInMixedFolder;
                 return !IsInMixedFolder;
             }
             }
         }
         }
+
+        public Book()
+        {
+            Tags = new List<string>();
+        }
     }
     }
 }
 }

+ 10 - 0
MediaBrowser.Controller/Entities/IHasMediaStreams.cs

@@ -0,0 +1,10 @@
+
+namespace MediaBrowser.Controller.Entities
+{
+    /// <summary>
+    /// This is essentially a marker interface
+    /// </summary>
+    public interface IHasMediaStreams
+    {
+    }
+}

+ 5 - 0
MediaBrowser.Controller/Entities/IHasScreenshots.cs

@@ -12,5 +12,10 @@ namespace MediaBrowser.Controller.Entities
         /// </summary>
         /// </summary>
         /// <value>The screenshot image paths.</value>
         /// <value>The screenshot image paths.</value>
         List<string> ScreenshotImagePaths { get; set; }
         List<string> ScreenshotImagePaths { get; set; }
+
+        /// <summary>
+        /// Validates the screenshots.
+        /// </summary>
+        void ValidateScreenshots();
     }
     }
 }
 }

+ 19 - 18
MediaBrowser.Controller/Entities/Video.cs

@@ -22,11 +22,28 @@ namespace MediaBrowser.Controller.Entities
 
 
         public Video()
         public Video()
         {
         {
-            MediaStreams = new List<MediaStream>();
             PlayableStreamFileNames = new List<string>();
             PlayableStreamFileNames = new List<string>();
             AdditionalPartIds = new List<Guid>();
             AdditionalPartIds = new List<Guid>();
         }
         }
 
 
+        /// <summary>
+        /// Gets or sets a value indicating whether this instance has subtitles.
+        /// </summary>
+        /// <value><c>true</c> if this instance has subtitles; otherwise, <c>false</c>.</value>
+        public bool HasSubtitles { get; set; }
+
+        /// <summary>
+        /// Gets or sets the video bit rate.
+        /// </summary>
+        /// <value>The video bit rate.</value>
+        public int? VideoBitRate { get; set; }
+
+        /// <summary>
+        /// Gets or sets the default index of the video stream.
+        /// </summary>
+        /// <value>The default index of the video stream.</value>
+        public int? DefaultVideoStreamIndex { get; set; }
+
         /// <summary>
         /// <summary>
         /// Gets or sets the type of the video.
         /// Gets or sets the type of the video.
         /// </summary>
         /// </summary>
@@ -45,12 +62,6 @@ namespace MediaBrowser.Controller.Entities
         /// <value>The video3 D format.</value>
         /// <value>The video3 D format.</value>
         public Video3DFormat? Video3DFormat { get; set; }
         public Video3DFormat? Video3DFormat { get; set; }
 
 
-        /// <summary>
-        /// Gets or sets the media streams.
-        /// </summary>
-        /// <value>The media streams.</value>
-        public List<MediaStream> MediaStreams { get; set; }
-
         /// <summary>
         /// <summary>
         /// If the video is a folder-rip, this will hold the file list for the largest playlist
         /// If the video is a folder-rip, this will hold the file list for the largest playlist
         /// </summary>
         /// </summary>
@@ -70,7 +81,7 @@ namespace MediaBrowser.Controller.Entities
         /// </summary>
         /// </summary>
         /// <value>The aspect ratio.</value>
         /// <value>The aspect ratio.</value>
         public string AspectRatio { get; set; }
         public string AspectRatio { get; set; }
-        
+
         /// <summary>
         /// <summary>
         /// Should be overridden to return the proper folder where metadata lives
         /// Should be overridden to return the proper folder where metadata lives
         /// </summary>
         /// </summary>
@@ -122,16 +133,6 @@ namespace MediaBrowser.Controller.Entities
                 .ToList();
                 .ToList();
         }
         }
 
 
-        /// <summary>
-        /// The default video stream for this video.  Use this to determine media info for this item.
-        /// </summary>
-        /// <value>The default video stream.</value>
-        [IgnoreDataMember]
-        public MediaStream DefaultVideoStream
-        {
-            get { return MediaStreams != null ? MediaStreams.FirstOrDefault(s => s.Type == MediaStreamType.Video) : null; }
-        }
-
         /// <summary>
         /// <summary>
         /// Gets a value indicating whether [is3 D].
         /// Gets a value indicating whether [is3 D].
         /// </summary>
         /// </summary>

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

@@ -93,6 +93,7 @@
     <Compile Include="Entities\IHasBudget.cs" />
     <Compile Include="Entities\IHasBudget.cs" />
     <Compile Include="Entities\IHasCriticRating.cs" />
     <Compile Include="Entities\IHasCriticRating.cs" />
     <Compile Include="Entities\IHasLanguage.cs" />
     <Compile Include="Entities\IHasLanguage.cs" />
+    <Compile Include="Entities\IHasMediaStreams.cs" />
     <Compile Include="Entities\IHasProductionLocations.cs" />
     <Compile Include="Entities\IHasProductionLocations.cs" />
     <Compile Include="Entities\IHasScreenshots.cs" />
     <Compile Include="Entities\IHasScreenshots.cs" />
     <Compile Include="Entities\IHasSoundtracks.cs" />
     <Compile Include="Entities\IHasSoundtracks.cs" />
@@ -124,6 +125,7 @@
     <Compile Include="Localization\ILocalizationManager.cs" />
     <Compile Include="Localization\ILocalizationManager.cs" />
     <Compile Include="Notifications\INotificationsRepository.cs" />
     <Compile Include="Notifications\INotificationsRepository.cs" />
     <Compile Include="Notifications\NotificationUpdateEventArgs.cs" />
     <Compile Include="Notifications\NotificationUpdateEventArgs.cs" />
+    <Compile Include="Persistence\MediaStreamQuery.cs" />
     <Compile Include="Providers\IDynamicInfoProvider.cs" />
     <Compile Include="Providers\IDynamicInfoProvider.cs" />
     <Compile Include="Providers\IImageProvider.cs" />
     <Compile Include="Providers\IImageProvider.cs" />
     <Compile Include="Providers\NameParser.cs" />
     <Compile Include="Providers\NameParser.cs" />

+ 14 - 4
MediaBrowser.Controller/MediaInfo/FFMpegManager.cs

@@ -93,7 +93,7 @@ namespace MediaBrowser.Controller.MediaInfo
                 return Path.Combine(_appPaths.CachePath, "subtitles");
                 return Path.Combine(_appPaths.CachePath, "subtitles");
             }
             }
         }
         }
-        
+
         /// <summary>
         /// <summary>
         /// The first chapter ticks
         /// The first chapter ticks
         /// </summary>
         /// </summary>
@@ -112,7 +112,7 @@ namespace MediaBrowser.Controller.MediaInfo
         public async Task<bool> PopulateChapterImages(Video video, List<ChapterInfo> chapters, bool extractImages, bool saveChapters, CancellationToken cancellationToken)
         public async Task<bool> PopulateChapterImages(Video video, List<ChapterInfo> chapters, bool extractImages, bool saveChapters, CancellationToken cancellationToken)
         {
         {
             // Can't extract images if there are no video streams
             // Can't extract images if there are no video streams
-            if (video.MediaStreams == null || video.MediaStreams.All(m => m.Type != MediaStreamType.Video))
+            if (!video.DefaultVideoStreamIndex.HasValue)
             {
             {
                 return true;
                 return true;
             }
             }
@@ -164,7 +164,7 @@ namespace MediaBrowser.Controller.MediaInfo
                             var parentPath = Path.GetDirectoryName(path);
                             var parentPath = Path.GetDirectoryName(path);
 
 
                             Directory.CreateDirectory(parentPath);
                             Directory.CreateDirectory(parentPath);
-                            
+
                             await _encoder.ExtractImage(inputPath, type, video.Video3DFormat, time, path, cancellationToken).ConfigureAwait(false);
                             await _encoder.ExtractImage(inputPath, type, video.Video3DFormat, time, path, cancellationToken).ConfigureAwait(false);
                             chapter.ImagePath = path;
                             chapter.ImagePath = path;
                             changesMade = true;
                             changesMade = true;
@@ -203,7 +203,17 @@ namespace MediaBrowser.Controller.MediaInfo
         {
         {
             var ticksParam = offset.HasValue ? "_" + offset.Value.Ticks : "";
             var ticksParam = offset.HasValue ? "_" + offset.Value.Ticks : "";
 
 
-            var stream = input.MediaStreams[subtitleStreamIndex];
+            var stream = _itemRepo.GetMediaStreams(new MediaStreamQuery
+            {
+                ItemId = input.Id,
+                Index = subtitleStreamIndex
+
+            }).FirstOrDefault();
+
+            if (stream == null)
+            {
+                return null;
+            }
 
 
             if (stream.IsExternal)
             if (stream.IsExternal)
             {
             {

+ 16 - 0
MediaBrowser.Controller/Persistence/IItemRepository.cs

@@ -95,6 +95,22 @@ namespace MediaBrowser.Controller.Persistence
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
         Task SaveChildren(Guid parentId, IEnumerable<Guid> children, CancellationToken cancellationToken);
         Task SaveChildren(Guid parentId, IEnumerable<Guid> children, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Gets the media streams.
+        /// </summary>
+        /// <param name="query">The query.</param>
+        /// <returns>IEnumerable{MediaStream}.</returns>
+        IEnumerable<MediaStream> GetMediaStreams(MediaStreamQuery query);
+
+        /// <summary>
+        /// Saves the media streams.
+        /// </summary>
+        /// <param name="id">The identifier.</param>
+        /// <param name="streams">The streams.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        Task SaveMediaStreams(Guid id, IEnumerable<MediaStream> streams, CancellationToken cancellationToken);
     }
     }
 }
 }
 
 

+ 26 - 0
MediaBrowser.Controller/Persistence/MediaStreamQuery.cs

@@ -0,0 +1,26 @@
+using MediaBrowser.Model.Entities;
+using System;
+
+namespace MediaBrowser.Controller.Persistence
+{
+    public class MediaStreamQuery
+    {
+        /// <summary>
+        /// Gets or sets the type.
+        /// </summary>
+        /// <value>The type.</value>
+        public MediaStreamType? Type { get; set; }
+
+        /// <summary>
+        /// Gets or sets the index.
+        /// </summary>
+        /// <value>The index.</value>
+        public int? Index { get; set; }
+
+        /// <summary>
+        /// Gets or sets the item identifier.
+        /// </summary>
+        /// <value>The item identifier.</value>
+        public Guid ItemId { get; set; }
+    }
+}

+ 1 - 1
MediaBrowser.Controller/Resolvers/EntityResolutionHelper.cs

@@ -1,6 +1,5 @@
 using MediaBrowser.Common.IO;
 using MediaBrowser.Common.IO;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
@@ -46,6 +45,7 @@ namespace MediaBrowser.Controller.Resolvers
                 ".3gp",
                 ".3gp",
                 ".webm",
                 ".webm",
                 ".mts",
                 ".mts",
+                ".m2v",
                 ".rec"
                 ".rec"
         };
         };
 
 

+ 0 - 3
MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj

@@ -143,9 +143,6 @@
     <Compile Include="..\MediaBrowser.Model\Entities\DisplayPreferences.cs">
     <Compile Include="..\MediaBrowser.Model\Entities\DisplayPreferences.cs">
       <Link>Entities\DisplayPreferences.cs</Link>
       <Link>Entities\DisplayPreferences.cs</Link>
     </Compile>
     </Compile>
-    <Compile Include="..\MediaBrowser.Model\Entities\IHasMediaStreams.cs">
-      <Link>Entities\IHasMediaStreams.cs</Link>
-    </Compile>
     <Compile Include="..\MediaBrowser.Model\Entities\IHasProviderIds.cs">
     <Compile Include="..\MediaBrowser.Model\Entities\IHasProviderIds.cs">
       <Link>Entities\IHasProviderIds.cs</Link>
       <Link>Entities\IHasProviderIds.cs</Link>
     </Compile>
     </Compile>

+ 0 - 3
MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj

@@ -130,9 +130,6 @@
     <Compile Include="..\MediaBrowser.Model\Entities\DisplayPreferences.cs">
     <Compile Include="..\MediaBrowser.Model\Entities\DisplayPreferences.cs">
       <Link>Entities\DisplayPreferences.cs</Link>
       <Link>Entities\DisplayPreferences.cs</Link>
     </Compile>
     </Compile>
-    <Compile Include="..\MediaBrowser.Model\Entities\IHasMediaStreams.cs">
-      <Link>Entities\IHasMediaStreams.cs</Link>
-    </Compile>
     <Compile Include="..\MediaBrowser.Model\Entities\IHasProviderIds.cs">
     <Compile Include="..\MediaBrowser.Model\Entities\IHasProviderIds.cs">
       <Link>Entities\IHasProviderIds.cs</Link>
       <Link>Entities\IHasProviderIds.cs</Link>
     </Compile>
     </Compile>

+ 0 - 3
MediaBrowser.Model/Dto/ItemCounts.cs

@@ -68,9 +68,6 @@ namespace MediaBrowser.Model.Dto
         /// </summary>
         /// </summary>
         /// <value>The unique types.</value>
         /// <value>The unique types.</value>
         public List<string> UniqueTypes { get; set; }
         public List<string> UniqueTypes { get; set; }
-
-        public int PersonCount { get; set; }
-        public int ArtistCount { get; set; }
         
         
         public ItemCounts()
         public ItemCounts()
         {
         {

+ 0 - 26
MediaBrowser.Model/Entities/IHasMediaStreams.cs

@@ -1,26 +0,0 @@
-using System.Collections.Generic;
-
-namespace MediaBrowser.Model.Entities
-{
-    /// <summary>
-    /// This is essentially a marker interface
-    /// </summary>
-    public interface IHasMediaStreams
-    {
-        /// <summary>
-        /// Gets or sets the media streams.
-        /// </summary>
-        /// <value>The media streams.</value>
-        List<MediaStream> MediaStreams { get; set; }
-        /// <summary>
-        /// Gets or sets the path.
-        /// </summary>
-        /// <value>The path.</value>
-        string Path { get; set; }
-        /// <summary>
-        /// Gets or sets the primary image path.
-        /// </summary>
-        /// <value>The primary image path.</value>
-        string PrimaryImagePath { get; }
-    }
-}

+ 3 - 3
MediaBrowser.Model/Entities/MediaStream.cs

@@ -19,10 +19,10 @@ namespace MediaBrowser.Model.Entities
         public string Language { get; set; }
         public string Language { get; set; }
 
 
         /// <summary>
         /// <summary>
-        /// Gets or sets the type of the scan.
+        /// Gets or sets a value indicating whether this instance is interlaced.
         /// </summary>
         /// </summary>
-        /// <value>The type of the scan.</value>
-        public string ScanType { get; set; }
+        /// <value><c>true</c> if this instance is interlaced; otherwise, <c>false</c>.</value>
+        public bool IsInterlaced { get; set; }
 
 
         /// <summary>
         /// <summary>
         /// Gets or sets the channel layout.
         /// Gets or sets the channel layout.

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

@@ -125,7 +125,6 @@
     <Compile Include="Entities\ParentalRating.cs" />
     <Compile Include="Entities\ParentalRating.cs" />
     <Compile Include="Dto\StreamOptions.cs" />
     <Compile Include="Dto\StreamOptions.cs" />
     <Compile Include="Entities\VirtualFolderInfo.cs" />
     <Compile Include="Entities\VirtualFolderInfo.cs" />
-    <Compile Include="Entities\IHasMediaStreams.cs" />
     <Compile Include="Extensions\ModelExtensions.cs" />
     <Compile Include="Extensions\ModelExtensions.cs" />
     <Compile Include="IO\IZipClient.cs" />
     <Compile Include="IO\IZipClient.cs" />
     <Compile Include="Logging\ILogger.cs" />
     <Compile Include="Logging\ILogger.cs" />

+ 6 - 1
MediaBrowser.Providers/ImageFromMediaLocationProvider.cs

@@ -108,7 +108,12 @@ namespace MediaBrowser.Providers
 
 
             // Make sure current backdrop paths still exist
             // Make sure current backdrop paths still exist
             item.ValidateBackdrops();
             item.ValidateBackdrops();
-            item.ValidateScreenshots();
+
+            var hasScreenshots = item as IHasScreenshots;
+            if (hasScreenshots != null)
+            {
+                hasScreenshots.ValidateScreenshots();
+            }
 
 
             cancellationToken.ThrowIfCancellationRequested();
             cancellationToken.ThrowIfCancellationRequested();
 
 

+ 1 - 1
MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs

@@ -127,7 +127,7 @@ namespace MediaBrowser.Providers.MediaInfo
 
 
             var audio = (Audio)item;
             var audio = (Audio)item;
 
 
-            if (string.IsNullOrEmpty(audio.PrimaryImagePath) && audio.MediaStreams.Any(s => s.Type == MediaStreamType.Video))
+            if (string.IsNullOrEmpty(audio.PrimaryImagePath) && audio.HasEmbeddedImage)
             {
             {
                 try
                 try
                 {
                 {

+ 14 - 11
MediaBrowser.Providers/MediaInfo/FFProbeAudioInfoProvider.cs

@@ -3,6 +3,7 @@ using MediaBrowser.Common.MediaInfo;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Serialization;
 using MediaBrowser.Model.Serialization;
@@ -19,9 +20,12 @@ namespace MediaBrowser.Providers.MediaInfo
     /// </summary>
     /// </summary>
     public class FFProbeAudioInfoProvider : BaseFFProbeProvider<Audio>
     public class FFProbeAudioInfoProvider : BaseFFProbeProvider<Audio>
     {
     {
-        public FFProbeAudioInfoProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IMediaEncoder mediaEncoder, IJsonSerializer jsonSerializer)
+        private readonly IItemRepository _itemRepo;
+
+        public FFProbeAudioInfoProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IMediaEncoder mediaEncoder, IJsonSerializer jsonSerializer, IItemRepository itemRepo)
             : base(logManager, configurationManager, mediaEncoder, jsonSerializer)
             : base(logManager, configurationManager, mediaEncoder, jsonSerializer)
         {
         {
+            _itemRepo = itemRepo;
         }
         }
 
 
         public override async Task<bool> FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken)
         public override async Task<bool> FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken)
@@ -38,7 +42,7 @@ namespace MediaBrowser.Providers.MediaInfo
 
 
             cancellationToken.ThrowIfCancellationRequested();
             cancellationToken.ThrowIfCancellationRequested();
 
 
-            Fetch(myItem, cancellationToken, result);
+            await Fetch(myItem, cancellationToken, result).ConfigureAwait(false);
 
 
             SetLastRefreshed(item, DateTime.UtcNow);
             SetLastRefreshed(item, DateTime.UtcNow);
 
 
@@ -51,22 +55,19 @@ namespace MediaBrowser.Providers.MediaInfo
         /// <param name="audio">The audio.</param>
         /// <param name="audio">The audio.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="data">The data.</param>
         /// <param name="data">The data.</param>
-        /// <param name="isoMount">The iso mount.</param>
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
-        protected void Fetch(Audio audio, CancellationToken cancellationToken, MediaInfoResult data)
+        protected Task Fetch(Audio audio, CancellationToken cancellationToken, MediaInfoResult data)
         {
         {
-            if (data.streams == null)
-            {
-                Logger.Error("Audio item has no streams: " + audio.Path);
-                return;
-            }
+            var internalStreams = data.streams ?? new MediaStreamInfo[] { };
 
 
-            audio.MediaStreams = data.streams.Select(s => GetMediaStream(s, data.format))
+            var mediaStreams = internalStreams.Select(s => GetMediaStream(s, data.format))
                 .Where(i => i != null)
                 .Where(i => i != null)
                 .ToList();
                 .ToList();
 
 
+            audio.HasEmbeddedImage = mediaStreams.Any(i => i.Type == MediaStreamType.Video);
+
             // Get the first audio stream
             // Get the first audio stream
-            var stream = data.streams.FirstOrDefault(s => s.codec_type.Equals("audio", StringComparison.OrdinalIgnoreCase));
+            var stream = internalStreams.FirstOrDefault(s => s.codec_type.Equals("audio", StringComparison.OrdinalIgnoreCase));
 
 
             if (stream != null)
             if (stream != null)
             {
             {
@@ -90,6 +91,8 @@ namespace MediaBrowser.Providers.MediaInfo
             {
             {
                 FetchDataFromTags(audio, data.format.tags);
                 FetchDataFromTags(audio, data.format.tags);
             }
             }
+
+            return _itemRepo.SaveMediaStreams(audio.Id, mediaStreams, cancellationToken);
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 35 - 21
MediaBrowser.Providers/MediaInfo/FFProbeVideoInfoProvider.cs

@@ -304,11 +304,17 @@ namespace MediaBrowser.Providers.MediaInfo
                 }
                 }
             }
             }
 
 
+            List<MediaStream> mediaStreams;
+
             if (data.streams != null)
             if (data.streams != null)
             {
             {
-                video.MediaStreams = data.streams.Select(s => GetMediaStream(s, data.format))
-                .Where(i => i != null)
-                .ToList();
+                mediaStreams = data.streams.Select(s => GetMediaStream(s, data.format))
+                    .Where(i => i != null)
+                    .ToList();
+            }
+            else
+            {
+                mediaStreams = new List<MediaStream>();
             }
             }
 
 
             var chapters = data.Chapters ?? new List<ChapterInfo>();
             var chapters = data.Chapters ?? new List<ChapterInfo>();
@@ -316,22 +322,28 @@ namespace MediaBrowser.Providers.MediaInfo
             if (video.VideoType == VideoType.BluRay || (video.IsoType.HasValue && video.IsoType.Value == IsoType.BluRay))
             if (video.VideoType == VideoType.BluRay || (video.IsoType.HasValue && video.IsoType.Value == IsoType.BluRay))
             {
             {
                 var inputPath = isoMount != null ? isoMount.MountedPath : video.Path;
                 var inputPath = isoMount != null ? isoMount.MountedPath : video.Path;
-                FetchBdInfo(video, chapters, inputPath, cancellationToken);
+                FetchBdInfo(video, chapters, mediaStreams, inputPath, cancellationToken);
             }
             }
 
 
-            AddExternalSubtitles(video);
+            AddExternalSubtitles(video, mediaStreams);
 
 
             FetchWtvInfo(video, force, data);
             FetchWtvInfo(video, force, data);
 
 
-            video.IsHD = video.MediaStreams.Any(i => i.Type == MediaStreamType.Video && i.Width.HasValue && i.Width.Value >= 1270);
+            video.IsHD = mediaStreams.Any(i => i.Type == MediaStreamType.Video && i.Width.HasValue && i.Width.Value >= 1270);
 
 
-            if (chapters.Count == 0 && video.MediaStreams.Any(i => i.Type == MediaStreamType.Video))
+            if (chapters.Count == 0 && mediaStreams.Any(i => i.Type == MediaStreamType.Video))
             {
             {
                 AddDummyChapters(video, chapters);
                 AddDummyChapters(video, chapters);
             }
             }
 
 
-            await Kernel.Instance.FFMpegManager.PopulateChapterImages(video, chapters, false, false, cancellationToken).ConfigureAwait(false);
+            var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);
+
+            video.VideoBitRate = videoStream == null ? null : videoStream.BitRate;
+            video.DefaultVideoStreamIndex = videoStream == null ? (int?)null : videoStream.Index;
 
 
+            video.HasSubtitles = mediaStreams.Any(i => i.Type == MediaStreamType.Subtitle);
+
+            await Kernel.Instance.FFMpegManager.PopulateChapterImages(video, chapters, false, false, cancellationToken).ConfigureAwait(false);
 
 
             BaseProviderInfo providerInfo;
             BaseProviderInfo providerInfo;
             var videoFileChanged = false;
             var videoFileChanged = false;
@@ -341,6 +353,8 @@ namespace MediaBrowser.Providers.MediaInfo
                 videoFileChanged = CompareDate(video) > providerInfo.LastRefreshed;
                 videoFileChanged = CompareDate(video) > providerInfo.LastRefreshed;
             }
             }
 
 
+            await _itemRepo.SaveMediaStreams(video.Id, mediaStreams, cancellationToken).ConfigureAwait(false);
+
             // Only save chapters if forcing, if the video changed, or if there are not already any saved ones
             // Only save chapters if forcing, if the video changed, or if there are not already any saved ones
             if (force || videoFileChanged || _itemRepo.GetChapter(video.Id, 0) == null)
             if (force || videoFileChanged || _itemRepo.GetChapter(video.Id, 0) == null)
             {
             {
@@ -439,7 +453,8 @@ namespace MediaBrowser.Providers.MediaInfo
         /// Adds the external subtitles.
         /// Adds the external subtitles.
         /// </summary>
         /// </summary>
         /// <param name="video">The video.</param>
         /// <param name="video">The video.</param>
-        private void AddExternalSubtitles(Video video)
+        /// <param name="currentStreams">The current streams.</param>
+        private void AddExternalSubtitles(Video video, List<MediaStream> currentStreams)
         {
         {
             var useParent = !video.ResolveArgs.IsDirectory;
             var useParent = !video.ResolveArgs.IsDirectory;
 
 
@@ -452,7 +467,7 @@ namespace MediaBrowser.Providers.MediaInfo
                                          ? video.Parent.ResolveArgs.FileSystemChildren
                                          ? video.Parent.ResolveArgs.FileSystemChildren
                                          : video.ResolveArgs.FileSystemChildren;
                                          : video.ResolveArgs.FileSystemChildren;
 
 
-            var startIndex = video.MediaStreams == null ? 0 : video.MediaStreams.Count;
+            var startIndex = currentStreams.Count;
             var streams = new List<MediaStream>();
             var streams = new List<MediaStream>();
 
 
             var videoFileNameWithoutExtension = Path.GetFileNameWithoutExtension(video.Path);
             var videoFileNameWithoutExtension = Path.GetFileNameWithoutExtension(video.Path);
@@ -503,11 +518,7 @@ namespace MediaBrowser.Providers.MediaInfo
                 }
                 }
             }
             }
 
 
-            if (video.MediaStreams == null)
-            {
-                video.MediaStreams = new List<MediaStream>();
-            }
-            video.MediaStreams.AddRange(streams);
+            currentStreams.AddRange(streams);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -556,9 +567,10 @@ namespace MediaBrowser.Providers.MediaInfo
         /// </summary>
         /// </summary>
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
         /// <param name="chapters">The chapters.</param>
         /// <param name="chapters">The chapters.</param>
+        /// <param name="mediaStreams">The media streams.</param>
         /// <param name="inputPath">The input path.</param>
         /// <param name="inputPath">The input path.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
-        private void FetchBdInfo(BaseItem item, List<ChapterInfo> chapters, string inputPath, CancellationToken cancellationToken)
+        private void FetchBdInfo(BaseItem item, List<ChapterInfo> chapters, List<MediaStream> mediaStreams, string inputPath, CancellationToken cancellationToken)
         {
         {
             var video = (Video)item;
             var video = (Video)item;
 
 
@@ -570,7 +582,7 @@ namespace MediaBrowser.Providers.MediaInfo
             int? currentWidth = null;
             int? currentWidth = null;
             int? currentBitRate = null;
             int? currentBitRate = null;
 
 
-            var videoStream = video.MediaStreams.FirstOrDefault(s => s.Type == MediaStreamType.Video);
+            var videoStream = mediaStreams.FirstOrDefault(s => s.Type == MediaStreamType.Video);
 
 
             // Grab the values that ffprobe recorded
             // Grab the values that ffprobe recorded
             if (videoStream != null)
             if (videoStream != null)
@@ -581,9 +593,9 @@ namespace MediaBrowser.Providers.MediaInfo
             }
             }
 
 
             // Fill video properties from the BDInfo result
             // Fill video properties from the BDInfo result
-            Fetch(video, result, chapters);
+            Fetch(video, mediaStreams, result, chapters);
 
 
-            videoStream = video.MediaStreams.FirstOrDefault(s => s.Type == MediaStreamType.Video);
+            videoStream = mediaStreams.FirstOrDefault(s => s.Type == MediaStreamType.Video);
 
 
             // Use the ffprobe values if these are empty
             // Use the ffprobe values if these are empty
             if (videoStream != null)
             if (videoStream != null)
@@ -608,13 +620,15 @@ namespace MediaBrowser.Providers.MediaInfo
         /// Fills video properties from the VideoStream of the largest playlist
         /// Fills video properties from the VideoStream of the largest playlist
         /// </summary>
         /// </summary>
         /// <param name="video">The video.</param>
         /// <param name="video">The video.</param>
+        /// <param name="mediaStreams">The media streams.</param>
         /// <param name="stream">The stream.</param>
         /// <param name="stream">The stream.</param>
         /// <param name="chapters">The chapters.</param>
         /// <param name="chapters">The chapters.</param>
-        private void Fetch(Video video, BlurayDiscInfo stream, List<ChapterInfo> chapters)
+        private void Fetch(Video video, List<MediaStream> mediaStreams, BlurayDiscInfo stream, List<ChapterInfo> chapters)
         {
         {
             // Check all input for null/empty/zero
             // Check all input for null/empty/zero
 
 
-            video.MediaStreams = stream.MediaStreams;
+            mediaStreams.Clear();
+            mediaStreams.AddRange(stream.MediaStreams);
 
 
             video.MainFeaturePlaylistName = stream.PlaylistName;
             video.MainFeaturePlaylistName = stream.PlaylistName;
 
 

+ 1 - 1
MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs

@@ -128,7 +128,7 @@ namespace MediaBrowser.Providers.MediaInfo
             }
             }
 
 
             // Can't extract if we didn't find a video stream in the file
             // Can't extract if we didn't find a video stream in the file
-            if (item.MediaStreams.All(m => m.Type != MediaStreamType.Video))
+            if (!item.DefaultVideoStreamIndex.HasValue)
             {
             {
                 return false;
                 return false;
             }
             }

+ 24 - 93
MediaBrowser.Providers/Savers/XmlSaverHelpers.cs

@@ -529,8 +529,6 @@ namespace MediaBrowser.Providers.Savers
         /// Appends the media info.
         /// Appends the media info.
         /// </summary>
         /// </summary>
         /// <typeparam name="T"></typeparam>
         /// <typeparam name="T"></typeparam>
-        /// <param name="item">The item.</param>
-        /// <param name="builder">The builder.</param>
         public static void AddMediaInfo<T>(T item, StringBuilder builder, IItemRepository itemRepository)
         public static void AddMediaInfo<T>(T item, StringBuilder builder, IItemRepository itemRepository)
             where T : BaseItem, IHasMediaStreams
             where T : BaseItem, IHasMediaStreams
         {
         {
@@ -538,105 +536,38 @@ namespace MediaBrowser.Providers.Savers
 
 
             builder.Append("<MediaInfo>");
             builder.Append("<MediaInfo>");
 
 
-            foreach (var stream in item.MediaStreams)
-            {
-                builder.Append("<" + stream.Type + ">");
-
-                if (!string.IsNullOrEmpty(stream.Codec))
-                {
-                    builder.Append("<Codec>" + SecurityElement.Escape(stream.Codec) + "</Codec>");
-                    builder.Append("<FFCodec>" + SecurityElement.Escape(stream.Codec) + "</FFCodec>");
-                }
-
-                if (stream.BitRate.HasValue)
-                {
-                    builder.Append("<BitRate>" + stream.BitRate.Value.ToString(UsCulture) + "</BitRate>");
-                }
-
-                if (stream.Width.HasValue)
-                {
-                    builder.Append("<Width>" + stream.Width.Value.ToString(UsCulture) + "</Width>");
-                }
-
-                if (stream.Height.HasValue)
-                {
-                    builder.Append("<Height>" + stream.Height.Value.ToString(UsCulture) + "</Height>");
-                }
-
-                if (!string.IsNullOrEmpty(stream.AspectRatio))
-                {
-                    builder.Append("<AspectRatio>" + SecurityElement.Escape(stream.AspectRatio) + "</AspectRatio>");
-                }
-
-                var framerate = stream.AverageFrameRate ?? stream.RealFrameRate;
-
-                if (framerate.HasValue)
-                {
-                    builder.Append("<FrameRate>" + framerate.Value.ToString(UsCulture) + "</FrameRate>");
-                }
-
-                if (!string.IsNullOrEmpty(stream.Language))
-                {
-                    builder.Append("<Language>" + SecurityElement.Escape(stream.Language) + "</Language>");
-                }
+            builder.Append("<Video>");
 
 
-                if (!string.IsNullOrEmpty(stream.ScanType))
-                {
-                    builder.Append("<ScanType>" + SecurityElement.Escape(stream.ScanType) + "</ScanType>");
-                }
-
-                if (stream.Channels.HasValue)
-                {
-                    builder.Append("<Channels>" + stream.Channels.Value.ToString(UsCulture) + "</Channels>");
-                }
-
-                if (stream.SampleRate.HasValue)
-                {
-                    builder.Append("<SamplingRate>" + stream.SampleRate.Value.ToString(UsCulture) + "</SamplingRate>");
-                }
+            if (item.RunTimeTicks.HasValue)
+            {
+                var timespan = TimeSpan.FromTicks(item.RunTimeTicks.Value);
 
 
-                builder.Append("<Default>" + SecurityElement.Escape(stream.IsDefault.ToString()) + "</Default>");
-                builder.Append("<Forced>" + SecurityElement.Escape(stream.IsForced.ToString()) + "</Forced>");
+                builder.Append("<Duration>" + Convert.ToInt64(timespan.TotalMinutes).ToString(UsCulture) + "</Duration>");
+                builder.Append("<DurationSeconds>" + Convert.ToInt64(timespan.TotalSeconds).ToString(UsCulture) + "</DurationSeconds>");
+            }
 
 
-                if (stream.Type == MediaStreamType.Video)
+            if (video != null && video.Video3DFormat.HasValue)
+            {
+                switch (video.Video3DFormat.Value)
                 {
                 {
-                    if (item.RunTimeTicks.HasValue)
-                    {
-                        var timespan = TimeSpan.FromTicks(item.RunTimeTicks.Value);
-
-                        builder.Append("<Duration>" + Convert.ToInt64(timespan.TotalMinutes).ToString(UsCulture) + "</Duration>");
-                        builder.Append("<DurationSeconds>" + Convert.ToInt64(timespan.TotalSeconds).ToString(UsCulture) + "</DurationSeconds>");
-                    }
-
-                    if (video != null && video.Video3DFormat.HasValue)
-                    {
-                        switch (video.Video3DFormat.Value)
-                        {
-                            case Video3DFormat.FullSideBySide:
-                                builder.Append("<Format3D>FSBS</Format3D>");
-                                break;
-                            case Video3DFormat.FullTopAndBottom:
-                                builder.Append("<Format3D>FTAB</Format3D>");
-                                break;
-                            case Video3DFormat.HalfSideBySide:
-                                builder.Append("<Format3D>HSBS</Format3D>");
-                                break;
-                            case Video3DFormat.HalfTopAndBottom:
-                                builder.Append("<Format3D>HTAB</Format3D>");
-                                break;
-                        }
-                    }
+                    case Video3DFormat.FullSideBySide:
+                        builder.Append("<Format3D>FSBS</Format3D>");
+                        break;
+                    case Video3DFormat.FullTopAndBottom:
+                        builder.Append("<Format3D>FTAB</Format3D>");
+                        break;
+                    case Video3DFormat.HalfSideBySide:
+                        builder.Append("<Format3D>HSBS</Format3D>");
+                        break;
+                    case Video3DFormat.HalfTopAndBottom:
+                        builder.Append("<Format3D>HTAB</Format3D>");
+                        break;
                 }
                 }
-
-                builder.Append("</" + stream.Type + ">");
             }
             }
 
 
-            builder.Append("</MediaInfo>");
+            builder.Append("</Video>");
 
 
-            if (video != null)
-            {
-                //AddChapters(video, builder, itemRepository);
-            }
+            builder.Append("</MediaInfo>");
         }
         }
     }
     }
 }
 }

+ 2 - 2
MediaBrowser.Server.Implementations/BdInfo/BdInfoExaminer.cs

@@ -102,7 +102,7 @@ namespace MediaBrowser.Server.Implementations.BdInfo
                 Width = videoStream.Width,
                 Width = videoStream.Width,
                 Height = videoStream.Height,
                 Height = videoStream.Height,
                 Codec = videoStream.CodecShortName,
                 Codec = videoStream.CodecShortName,
-                ScanType = videoStream.IsInterlaced ? "interlaced" : "progressive",
+                IsInterlaced = videoStream.IsInterlaced,
                 Type = MediaStreamType.Video,
                 Type = MediaStreamType.Video,
                 Index = streams.Count
                 Index = streams.Count
             };
             };
@@ -146,7 +146,7 @@ namespace MediaBrowser.Server.Implementations.BdInfo
             {
             {
                 stream.Channels = audioStream.ChannelCount + 1;
                 stream.Channels = audioStream.ChannelCount + 1;
             }
             }
-          
+
             streams.Add(stream);
             streams.Add(stream);
         }
         }
 
 

+ 5 - 1
MediaBrowser.Server.Implementations/Dto/DtoService.cs

@@ -1034,7 +1034,11 @@ namespace MediaBrowser.Server.Implementations.Dto
 
 
                 if (iHasMediaStreams != null)
                 if (iHasMediaStreams != null)
                 {
                 {
-                    dto.MediaStreams = iHasMediaStreams.MediaStreams;
+                    dto.MediaStreams = _itemRepo.GetMediaStreams(new MediaStreamQuery
+                    {
+                        ItemId = item.Id
+
+                    }).ToList();
                 }
                 }
             }
             }
 
 

+ 1 - 0
MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj

@@ -168,6 +168,7 @@
     <Compile Include="MediaEncoder\MediaEncoder.cs" />
     <Compile Include="MediaEncoder\MediaEncoder.cs" />
     <Compile Include="Persistence\SqliteChapterRepository.cs" />
     <Compile Include="Persistence\SqliteChapterRepository.cs" />
     <Compile Include="Persistence\SqliteExtensions.cs" />
     <Compile Include="Persistence\SqliteExtensions.cs" />
+    <Compile Include="Persistence\SqliteMediaStreamsRepository.cs" />
     <Compile Include="Persistence\SqliteNotificationsRepository.cs" />
     <Compile Include="Persistence\SqliteNotificationsRepository.cs" />
     <Compile Include="Persistence\TypeMapper.cs" />
     <Compile Include="Persistence\TypeMapper.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />

+ 24 - 0
MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs

@@ -56,6 +56,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
         private readonly string _criticReviewsPath;
         private readonly string _criticReviewsPath;
 
 
         private SqliteChapterRepository _chapterRepository;
         private SqliteChapterRepository _chapterRepository;
+        private SqliteMediaStreamsRepository _mediaStreamsRepository;
 
 
         private IDbCommand _deleteChildrenCommand;
         private IDbCommand _deleteChildrenCommand;
         private IDbCommand _saveChildrenCommand;
         private IDbCommand _saveChildrenCommand;
@@ -94,6 +95,12 @@ namespace MediaBrowser.Server.Implementations.Persistence
             var chapterConnection = SqliteExtensions.ConnectToDb(chapterDbFile, _logger).Result;
             var chapterConnection = SqliteExtensions.ConnectToDb(chapterDbFile, _logger).Result;
 
 
             _chapterRepository = new SqliteChapterRepository(chapterConnection, logManager);
             _chapterRepository = new SqliteChapterRepository(chapterConnection, logManager);
+
+            var mediaStreamsDbFile = Path.Combine(_appPaths.DataPath, "mediainfo.db");
+
+            var mediaStreamsConnection = SqliteExtensions.ConnectToDb(mediaStreamsDbFile, _logger).Result;
+
+            _mediaStreamsRepository = new SqliteMediaStreamsRepository(mediaStreamsConnection, logManager);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -122,6 +129,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
 
 
             PrepareStatements();
             PrepareStatements();
 
 
+            _mediaStreamsRepository.Initialize();
             _chapterRepository.Initialize();
             _chapterRepository.Initialize();
         }
         }
 
 
@@ -413,6 +421,12 @@ namespace MediaBrowser.Server.Implementations.Persistence
                     _chapterRepository.Dispose();
                     _chapterRepository.Dispose();
                     _chapterRepository = null;
                     _chapterRepository = null;
                 }
                 }
+
+                if (_mediaStreamsRepository != null)
+                {
+                    _mediaStreamsRepository.Dispose();
+                    _mediaStreamsRepository = null;
+                }
             }
             }
         }
         }
 
 
@@ -511,5 +525,15 @@ namespace MediaBrowser.Server.Implementations.Persistence
                 _writeLock.Release();
                 _writeLock.Release();
             }
             }
         }
         }
+
+        public IEnumerable<MediaStream> GetMediaStreams(MediaStreamQuery query)
+        {
+            return _mediaStreamsRepository.GetMediaStreams(query);
+        }
+
+        public Task SaveMediaStreams(Guid id, IEnumerable<MediaStream> streams, CancellationToken cancellationToken)
+        {
+            return _mediaStreamsRepository.SaveMediaStreams(id, streams, cancellationToken);
+        }
     }
     }
 }
 }

+ 377 - 0
MediaBrowser.Server.Implementations/Persistence/SqliteMediaStreamsRepository.cs

@@ -0,0 +1,377 @@
+using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Logging;
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Server.Implementations.Persistence
+{
+    class SqliteMediaStreamsRepository
+    {
+        private IDbConnection _connection;
+
+        private readonly ILogger _logger;
+
+        private IDbCommand _deleteStreamsCommand;
+        private IDbCommand _saveStreamCommand;
+
+        public SqliteMediaStreamsRepository(IDbConnection connection, ILogManager logManager)
+        {
+            _connection = connection;
+
+            _logger = logManager.GetLogger(GetType().Name);
+        }
+
+        /// <summary>
+        /// Opens the connection to the database
+        /// </summary>
+        /// <returns>Task.</returns>
+        public void Initialize()
+        {
+            var createTableCommand
+                = "create table if not exists mediastreams ";
+
+            createTableCommand += "(ItemId GUID, StreamIndex INT, StreamType TEXT, Codec TEXT, Language TEXT, ChannelLayout TEXT, Profile TEXT, AspectRatio TEXT, Path TEXT, IsInterlaced BIT, BitRate INT NULL, Channels INT NULL, SampleRate INT NULL, IsDefault BIT, IsForced BIT, IsExternal BIT, Height INT NULL, Width INT NULL, AverageFrameRate FLOAT NULL, RealFrameRate FLOAT NULL, Level FLOAT NULL, PRIMARY KEY (ItemId, StreamIndex))";
+
+            string[] queries = {
+
+                                createTableCommand,
+                                "create index if not exists idx_mediastreams on mediastreams(ItemId, StreamIndex)",
+
+                                //pragmas
+                                "pragma temp_store = memory"
+                               };
+
+            _connection.RunQueries(queries, _logger);
+
+            PrepareStatements();
+        }
+
+        private readonly string[] _saveColumns =
+        {
+            "ItemId",
+            "StreamIndex",
+            "StreamType",
+            "Codec",
+            "Language",
+            "ChannelLayout",
+            "Profile",
+            "AspectRatio",
+            "Path",
+            "IsInterlaced",
+            "BitRate",
+            "Channels",
+            "SampleRate",
+            "IsDefault",
+            "IsForced",
+            "IsExternal",
+            "Height",
+            "Width",
+            "AverageFrameRate",
+            "RealFrameRate",
+            "Level"
+        };
+
+        /// <summary>
+        /// The _write lock
+        /// </summary>
+        private readonly SemaphoreSlim _writeLock = new SemaphoreSlim(1, 1);
+
+        /// <summary>
+        /// Prepares the statements.
+        /// </summary>
+        private void PrepareStatements()
+        {
+            _deleteStreamsCommand = _connection.CreateCommand();
+            _deleteStreamsCommand.CommandText = "delete from mediastreams where ItemId=@ItemId";
+            _deleteStreamsCommand.Parameters.Add(_deleteStreamsCommand, "@ItemId");
+
+            _saveStreamCommand = _connection.CreateCommand();
+
+            _saveStreamCommand.CommandText = string.Format("replace into mediastreams ({0}) values ({1})",
+                string.Join(",", _saveColumns),
+                string.Join(",", _saveColumns.Select(i => "@" + i).ToArray()));
+
+            foreach (var col in _saveColumns)
+            {
+                _saveStreamCommand.Parameters.Add(_saveStreamCommand, "@" + col);
+            }
+        }
+
+        public IEnumerable<MediaStream> GetMediaStreams(MediaStreamQuery query)
+        {
+            if (query == null)
+            {
+                throw new ArgumentNullException("query");
+            }
+
+            using (var cmd = _connection.CreateCommand())
+            {
+                var cmdText = "select " + string.Join(",", _saveColumns) + " from mediastreams where";
+
+                cmdText += " ItemId=@ItemId";
+                cmd.Parameters.Add(cmd, "@ItemId", DbType.Guid).Value = query.ItemId;
+
+                if (query.Type.HasValue)
+                {
+                    cmdText += " AND StreamType=@StreamType";
+                    cmd.Parameters.Add(cmd, "@StreamType", DbType.String).Value = query.Type.Value.ToString();
+                }
+
+                if (query.Index.HasValue)
+                {
+                    cmdText += " AND StreamIndex=@StreamIndex";
+                    cmd.Parameters.Add(cmd, "@StreamIndex", DbType.Int32).Value = query.Index.Value;
+                }
+
+                cmdText += " order by StreamIndex ASC";
+
+                cmd.CommandText = cmdText;
+
+                using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult))
+                {
+                    while (reader.Read())
+                    {
+                        yield return GetMediaStream(reader);
+                    }
+                }
+            }
+        }
+
+        /// <summary>
+        /// Gets the chapter.
+        /// </summary>
+        /// <param name="reader">The reader.</param>
+        /// <returns>ChapterInfo.</returns>
+        private MediaStream GetMediaStream(IDataReader reader)
+        {
+            var item = new MediaStream
+            {
+                Index = reader.GetInt32(1)
+            };
+
+            item.Type = (MediaStreamType)Enum.Parse(typeof(MediaStreamType), reader.GetString(2), true);
+
+            if (!reader.IsDBNull(3))
+            {
+                item.Codec = reader.GetString(3);
+            }
+
+            if (!reader.IsDBNull(4))
+            {
+                item.Language = reader.GetString(4);
+            }
+
+            if (!reader.IsDBNull(5))
+            {
+                item.ChannelLayout = reader.GetString(5);
+            }
+
+            if (!reader.IsDBNull(6))
+            {
+                item.Profile = reader.GetString(6);
+            }
+
+            if (!reader.IsDBNull(7))
+            {
+                item.AspectRatio = reader.GetString(7);
+            }
+
+            if (!reader.IsDBNull(8))
+            {
+                item.Path = reader.GetString(8);
+            }
+
+            item.IsInterlaced = reader.GetBoolean(9);
+
+            if (!reader.IsDBNull(10))
+            {
+                item.BitRate = reader.GetInt32(10);
+            }
+
+            if (!reader.IsDBNull(11))
+            {
+                item.Channels = reader.GetInt32(11);
+            }
+
+            if (!reader.IsDBNull(12))
+            {
+                item.SampleRate = reader.GetInt32(12);
+            }
+
+            item.IsDefault = reader.GetBoolean(13);
+            item.IsForced = reader.GetBoolean(14);
+            item.IsExternal = reader.GetBoolean(15);
+
+            if (!reader.IsDBNull(16))
+            {
+                item.Width = reader.GetInt32(16);
+            }
+
+            if (!reader.IsDBNull(17))
+            {
+                item.Height = reader.GetInt32(17);
+            }
+
+            if (!reader.IsDBNull(18))
+            {
+                item.AverageFrameRate = reader.GetFloat(18);
+            }
+
+            if (!reader.IsDBNull(19))
+            {
+                item.RealFrameRate = reader.GetFloat(19);
+            }
+
+            if (!reader.IsDBNull(20))
+            {
+                item.Level = reader.GetFloat(20);
+            }
+
+            return item;
+        }
+
+        public async Task SaveMediaStreams(Guid id, IEnumerable<MediaStream> streams, CancellationToken cancellationToken)
+        {
+            if (id == Guid.Empty)
+            {
+                throw new ArgumentNullException("id");
+            }
+
+            if (streams == null)
+            {
+                throw new ArgumentNullException("streams");
+            }
+
+            cancellationToken.ThrowIfCancellationRequested();
+
+            await _writeLock.WaitAsync(cancellationToken).ConfigureAwait(false);
+
+            IDbTransaction transaction = null;
+
+            try
+            {
+                transaction = _connection.BeginTransaction();
+
+                // First delete chapters
+                _deleteStreamsCommand.GetParameter(0).Value = id;
+
+                _deleteStreamsCommand.Transaction = transaction;
+
+                _deleteStreamsCommand.ExecuteNonQuery();
+
+                foreach (var stream in streams)
+                {
+                    cancellationToken.ThrowIfCancellationRequested();
+
+                    _saveStreamCommand.GetParameter(0).Value = id;
+                    _saveStreamCommand.GetParameter(1).Value = stream.Index;
+                    _saveStreamCommand.GetParameter(2).Value = stream.Type.ToString();
+                    _saveStreamCommand.GetParameter(3).Value = stream.Codec;
+                    _saveStreamCommand.GetParameter(4).Value = stream.Language;
+                    _saveStreamCommand.GetParameter(5).Value = stream.ChannelLayout;
+                    _saveStreamCommand.GetParameter(6).Value = stream.Profile;
+                    _saveStreamCommand.GetParameter(7).Value = stream.AspectRatio;
+                    _saveStreamCommand.GetParameter(8).Value = stream.Path;
+
+                    _saveStreamCommand.GetParameter(9).Value = stream.IsInterlaced;
+
+                    _saveStreamCommand.GetParameter(10).Value = stream.BitRate;
+                    _saveStreamCommand.GetParameter(11).Value = stream.Channels;
+                    _saveStreamCommand.GetParameter(12).Value = stream.SampleRate;
+
+                    _saveStreamCommand.GetParameter(13).Value = stream.IsDefault;
+                    _saveStreamCommand.GetParameter(14).Value = stream.IsForced;
+                    _saveStreamCommand.GetParameter(15).Value = stream.IsExternal;
+
+                    _saveStreamCommand.GetParameter(16).Value = stream.Width;
+                    _saveStreamCommand.GetParameter(17).Value = stream.Height;
+                    _saveStreamCommand.GetParameter(18).Value = stream.AverageFrameRate;
+                    _saveStreamCommand.GetParameter(19).Value = stream.RealFrameRate;
+                    _saveStreamCommand.GetParameter(20).Value = stream.Level;
+                    
+                    _saveStreamCommand.Transaction = transaction;
+                    _saveStreamCommand.ExecuteNonQuery();
+                }
+
+                transaction.Commit();
+            }
+            catch (OperationCanceledException)
+            {
+                if (transaction != null)
+                {
+                    transaction.Rollback();
+                }
+
+                throw;
+            }
+            catch (Exception e)
+            {
+                _logger.ErrorException("Failed to save media streams:", e);
+
+                if (transaction != null)
+                {
+                    transaction.Rollback();
+                }
+
+                throw;
+            }
+            finally
+            {
+                if (transaction != null)
+                {
+                    transaction.Dispose();
+                }
+
+                _writeLock.Release();
+            }
+        }
+
+        /// <summary>
+        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
+        /// </summary>
+        public void Dispose()
+        {
+            Dispose(true);
+            GC.SuppressFinalize(this);
+        }
+
+        private readonly object _disposeLock = new object();
+
+        /// <summary>
+        /// Releases unmanaged and - optionally - managed resources.
+        /// </summary>
+        /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
+        protected virtual void Dispose(bool dispose)
+        {
+            if (dispose)
+            {
+                try
+                {
+                    lock (_disposeLock)
+                    {
+                        if (_connection != null)
+                        {
+                            if (_connection.IsOpen())
+                            {
+                                _connection.Close();
+                            }
+
+                            _connection.Dispose();
+                            _connection = null;
+                        }
+                    }
+                }
+                catch (Exception ex)
+                {
+                    _logger.ErrorException("Error disposing database", ex);
+                }
+            }
+        }
+    }
+}
+

+ 4 - 14
MediaBrowser.Server.Implementations/Persistence/SqliteUserDataRepository.cs

@@ -31,36 +31,26 @@ namespace MediaBrowser.Server.Implementations.Persistence
             }
             }
         }
         }
 
 
-        private readonly IJsonSerializer _jsonSerializer;
-
         /// <summary>
         /// <summary>
         /// The _app paths
         /// The _app paths
         /// </summary>
         /// </summary>
         private readonly IApplicationPaths _appPaths;
         private readonly IApplicationPaths _appPaths;
 
 
         /// <summary>
         /// <summary>
-        /// Initializes a new instance of the <see cref="SqliteUserDataRepository"/> class.
+        /// Initializes a new instance of the <see cref="SqliteUserDataRepository" /> class.
         /// </summary>
         /// </summary>
         /// <param name="appPaths">The app paths.</param>
         /// <param name="appPaths">The app paths.</param>
-        /// <param name="jsonSerializer">The json serializer.</param>
         /// <param name="logManager">The log manager.</param>
         /// <param name="logManager">The log manager.</param>
-        /// <exception cref="System.ArgumentNullException">
-        /// jsonSerializer
+        /// <exception cref="System.ArgumentNullException">jsonSerializer
         /// or
         /// or
-        /// appPaths
-        /// </exception>
-        public SqliteUserDataRepository(IApplicationPaths appPaths, IJsonSerializer jsonSerializer, ILogManager logManager)
+        /// appPaths</exception>
+        public SqliteUserDataRepository(IApplicationPaths appPaths, ILogManager logManager)
         {
         {
-            if (jsonSerializer == null)
-            {
-                throw new ArgumentNullException("jsonSerializer");
-            }
             if (appPaths == null)
             if (appPaths == null)
             {
             {
                 throw new ArgumentNullException("appPaths");
                 throw new ArgumentNullException("appPaths");
             }
             }
 
 
-            _jsonSerializer = jsonSerializer;
             _appPaths = appPaths;
             _appPaths = appPaths;
             _logger = logManager.GetLogger(GetType().Name);
             _logger = logManager.GetLogger(GetType().Name);
         }
         }

+ 1 - 1
MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs

@@ -101,7 +101,7 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks
 
 
             // Limit to video files to reduce changes of ffmpeg crash dialog
             // Limit to video files to reduce changes of ffmpeg crash dialog
             foreach (var item in newItems
             foreach (var item in newItems
-                .Where(i => i.LocationType == LocationType.FileSystem && i.VideoType == VideoType.VideoFile && string.IsNullOrEmpty(i.PrimaryImagePath) && i.MediaStreams.Any(m => m.Type == MediaStreamType.Video))
+                .Where(i => i.LocationType == LocationType.FileSystem && i.VideoType == VideoType.VideoFile && string.IsNullOrEmpty(i.PrimaryImagePath) && i.DefaultVideoStreamIndex.HasValue)
                 .Take(2))
                 .Take(2))
             {
             {
                 try
                 try

+ 2 - 10
MediaBrowser.Server.Implementations/Sorting/VideoBitRateComparer.cs

@@ -1,8 +1,6 @@
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Sorting;
 using MediaBrowser.Controller.Sorting;
-using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Querying;
 using MediaBrowser.Model.Querying;
-using System.Linq;
 
 
 namespace MediaBrowser.Server.Implementations.Sorting
 namespace MediaBrowser.Server.Implementations.Sorting
 {
 {
@@ -21,17 +19,11 @@ namespace MediaBrowser.Server.Implementations.Sorting
 
 
         private int GetValue(BaseItem item)
         private int GetValue(BaseItem item)
         {
         {
-            var video = item as IHasMediaStreams;
+            var video = item as Video;
 
 
             if (video != null)
             if (video != null)
             {
             {
-                var videoStream = video.MediaStreams
-                    .FirstOrDefault(i => i.Type == MediaStreamType.Video);
-
-                if (videoStream != null)
-                {
-                    return videoStream.BitRate ?? 0;
-                }
+                return video.VideoBitRate ?? 0;
             }
             }
 
 
             return 0;
             return 0;

+ 1 - 1
MediaBrowser.ServerApplication/ApplicationHost.cs

@@ -394,7 +394,7 @@ namespace MediaBrowser.ServerApplication
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
         private async Task ConfigureUserDataRepositories()
         private async Task ConfigureUserDataRepositories()
         {
         {
-            var repo = new SqliteUserDataRepository(ApplicationPaths, JsonSerializer, LogManager);
+            var repo = new SqliteUserDataRepository(ApplicationPaths, LogManager);
 
 
             await repo.Initialize().ConfigureAwait(false);
             await repo.Initialize().ConfigureAwait(false);
 
 

+ 1 - 1
MediaBrowser.ServerApplication/FFMpeg/FFMpegDownloadInfo.cs

@@ -3,7 +3,7 @@ namespace MediaBrowser.ServerApplication.FFMpeg
 {
 {
     public static class FFMpegDownloadInfo
     public static class FFMpegDownloadInfo
     {
     {
-        public static string Version = "ffmpeg20131110";
+        public static string Version = "ffmpeg20131110.1";
 
 
         public static string[] FfMpegUrls = new[]
         public static string[] FfMpegUrls = new[]
                 {
                 {