浏览代码

added modular configuration

Luke Pulverenti 11 年之前
父节点
当前提交
933443c2b9
共有 34 个文件被更改,包括 563 次插入126 次删除
  1. 37 1
      MediaBrowser.Api/ConfigurationService.cs
  2. 7 2
      MediaBrowser.Api/Dlna/DlnaServerService.cs
  3. 1 0
      MediaBrowser.Api/MediaBrowser.Api.csproj
  4. 1 0
      MediaBrowser.Common.Implementations/BaseApplicationHost.cs
  5. 82 9
      MediaBrowser.Common.Implementations/Configuration/BaseConfigurationManager.cs
  6. 18 0
      MediaBrowser.Common/Configuration/ConfigurationUpdateEventArgs.cs
  7. 17 0
      MediaBrowser.Common/Configuration/IConfigurationFactory.cs
  8. 42 1
      MediaBrowser.Common/Configuration/IConfigurationManager.cs
  9. 2 0
      MediaBrowser.Common/MediaBrowser.Common.csproj
  10. 5 0
      MediaBrowser.Common/Net/MimeTypes.cs
  11. 7 0
      MediaBrowser.Controller/Chapters/IChapterManager.cs
  12. 21 0
      MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
  13. 29 0
      MediaBrowser.Dlna/ConfigurationExtension.cs
  14. 4 2
      MediaBrowser.Dlna/ContentDirectory/ContentDirectory.cs
  15. 14 8
      MediaBrowser.Dlna/Main/DlnaEntryPoint.cs
  16. 1 0
      MediaBrowser.Dlna/MediaBrowser.Dlna.csproj
  17. 3 3
      MediaBrowser.Dlna/PlayTo/PlayToManager.cs
  18. 3 3
      MediaBrowser.Dlna/PlayTo/SsdpHttpClient.cs
  19. 1 1
      MediaBrowser.Dlna/Service/BaseControlHandler.cs
  20. 17 13
      MediaBrowser.Dlna/Ssdp/SsdpHandler.cs
  21. 93 6
      MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
  22. 5 6
      MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
  23. 9 0
      MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
  24. 9 0
      MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
  25. 18 0
      MediaBrowser.Model/Configuration/ChannelOptions.cs
  26. 27 0
      MediaBrowser.Model/Configuration/ChapterOptions.cs
  27. 9 52
      MediaBrowser.Model/Configuration/ServerConfiguration.cs
  28. 10 0
      MediaBrowser.Model/Configuration/SubtitlePlaybackMode.cs
  29. 0 8
      MediaBrowser.Model/Configuration/UserConfiguration.cs
  30. 3 0
      MediaBrowser.Model/MediaBrowser.Model.csproj
  31. 29 3
      MediaBrowser.Providers/Chapters/ChapterManager.cs
  32. 4 2
      MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
  33. 9 5
      MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs
  34. 26 1
      MediaBrowser.ServerApplication/ApplicationHost.cs

+ 37 - 1
MediaBrowser.Api/ConfigurationService.cs

@@ -8,8 +8,11 @@ using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Serialization;
 using MediaBrowser.Model.Serialization;
 using ServiceStack;
 using ServiceStack;
+using ServiceStack.Text.Controller;
+using ServiceStack.Web;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.IO;
 using System.Linq;
 using System.Linq;
 
 
 namespace MediaBrowser.Api
 namespace MediaBrowser.Api
@@ -23,6 +26,13 @@ namespace MediaBrowser.Api
 
 
     }
     }
 
 
+    [Route("/System/Configuration/{Key}", "GET", Summary = "Gets a named configuration")]
+    public class GetNamedConfiguration
+    {
+        [ApiMember(Name = "Key", Description = "Key", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
+        public string Key { get; set; }
+    }
+    
     /// <summary>
     /// <summary>
     /// Class UpdateConfiguration
     /// Class UpdateConfiguration
     /// </summary>
     /// </summary>
@@ -31,6 +41,15 @@ namespace MediaBrowser.Api
     {
     {
     }
     }
 
 
+    [Route("/System/Configuration/{Key}", "POST", Summary = "Updates named configuration")]
+    public class UpdateNamedConfiguration : IReturnVoid, IRequiresRequestStream
+    {
+        [ApiMember(Name = "Key", Description = "Key", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
+        public string Key { get; set; }
+
+        public Stream RequestStream { get; set; }
+    }
+    
     [Route("/System/Configuration/MetadataOptions/Default", "GET", Summary = "Gets a default MetadataOptions object")]
     [Route("/System/Configuration/MetadataOptions/Default", "GET", Summary = "Gets a default MetadataOptions object")]
     public class GetDefaultMetadataOptions : IReturn<MetadataOptions>
     public class GetDefaultMetadataOptions : IReturn<MetadataOptions>
     {
     {
@@ -88,6 +107,13 @@ namespace MediaBrowser.Api
             return ToOptimizedResultUsingCache(cacheKey, dateModified, null, () => _configurationManager.Configuration);
             return ToOptimizedResultUsingCache(cacheKey, dateModified, null, () => _configurationManager.Configuration);
         }
         }
 
 
+        public object Get(GetNamedConfiguration request)
+        {
+            var result = _configurationManager.GetConfiguration(request.Key);
+
+            return ToOptimizedResult(result);
+        }
+
         /// <summary>
         /// <summary>
         /// Posts the specified configuraiton.
         /// Posts the specified configuraiton.
         /// </summary>
         /// </summary>
@@ -95,7 +121,6 @@ namespace MediaBrowser.Api
         public void Post(UpdateConfiguration request)
         public void Post(UpdateConfiguration request)
         {
         {
             // Silly, but we need to serialize and deserialize or the XmlSerializer will write the xml with an element name of UpdateConfiguration
             // Silly, but we need to serialize and deserialize or the XmlSerializer will write the xml with an element name of UpdateConfiguration
-
             var json = _jsonSerializer.SerializeToString(request);
             var json = _jsonSerializer.SerializeToString(request);
 
 
             var config = _jsonSerializer.DeserializeFromString<ServerConfiguration>(json);
             var config = _jsonSerializer.DeserializeFromString<ServerConfiguration>(json);
@@ -103,6 +128,17 @@ namespace MediaBrowser.Api
             _configurationManager.ReplaceConfiguration(config);
             _configurationManager.ReplaceConfiguration(config);
         }
         }
 
 
+        public void Post(UpdateNamedConfiguration request)
+        {
+            var pathInfo = PathInfo.Parse(Request.PathInfo);
+            var key = pathInfo.GetArgumentValue<string>(2);
+
+            var configurationType = _configurationManager.GetConfigurationType(key);
+            var configuration = _jsonSerializer.DeserializeFromStream(request.RequestStream, configurationType);
+            
+            _configurationManager.SaveConfiguration(key, configuration);
+        }
+
         public object Get(GetDefaultMetadataOptions request)
         public object Get(GetDefaultMetadataOptions request)
         {
         {
             return ToOptimizedSerializedResultUsingCache(new MetadataOptions());
             return ToOptimizedSerializedResultUsingCache(new MetadataOptions());

+ 7 - 2
MediaBrowser.Api/Dlna/DlnaServerService.cs

@@ -1,4 +1,6 @@
-using MediaBrowser.Controller.Dlna;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Controller.Dlna;
+using MediaBrowser.Model.Configuration;
 using ServiceStack;
 using ServiceStack;
 using ServiceStack.Text.Controller;
 using ServiceStack.Text.Controller;
 using ServiceStack.Web;
 using ServiceStack.Web;
@@ -76,11 +78,14 @@ namespace MediaBrowser.Api.Dlna
         private readonly IContentDirectory _contentDirectory;
         private readonly IContentDirectory _contentDirectory;
         private readonly IConnectionManager _connectionManager;
         private readonly IConnectionManager _connectionManager;
 
 
-        public DlnaServerService(IDlnaManager dlnaManager, IContentDirectory contentDirectory, IConnectionManager connectionManager)
+        private readonly IConfigurationManager _config;
+
+        public DlnaServerService(IDlnaManager dlnaManager, IContentDirectory contentDirectory, IConnectionManager connectionManager, IConfigurationManager config)
         {
         {
             _dlnaManager = dlnaManager;
             _dlnaManager = dlnaManager;
             _contentDirectory = contentDirectory;
             _contentDirectory = contentDirectory;
             _connectionManager = connectionManager;
             _connectionManager = connectionManager;
+            _config = config;
         }
         }
 
 
         public object Get(GetDescriptionXml request)
         public object Get(GetDescriptionXml request)

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

@@ -101,6 +101,7 @@
     <Compile Include="NotificationsService.cs" />
     <Compile Include="NotificationsService.cs" />
     <Compile Include="PackageReviewService.cs" />
     <Compile Include="PackageReviewService.cs" />
     <Compile Include="PackageService.cs" />
     <Compile Include="PackageService.cs" />
+    <Compile Include="Playback\BifService.cs" />
     <Compile Include="Playback\EndlessStreamCopy.cs" />
     <Compile Include="Playback\EndlessStreamCopy.cs" />
     <Compile Include="Playback\Hls\BaseHlsService.cs" />
     <Compile Include="Playback\Hls\BaseHlsService.cs" />
     <Compile Include="Playback\Hls\DynamicHlsService.cs" />
     <Compile Include="Playback\Hls\DynamicHlsService.cs" />

+ 1 - 0
MediaBrowser.Common.Implementations/BaseApplicationHost.cs

@@ -342,6 +342,7 @@ namespace MediaBrowser.Common.Implementations
         /// </summary>
         /// </summary>
         protected virtual void FindParts()
         protected virtual void FindParts()
         {
         {
+            ConfigurationManager.AddParts(GetExports<IConfigurationFactory>());
             Plugins = GetExports<IPlugin>();
             Plugins = GetExports<IPlugin>();
         }
         }
 
 

+ 82 - 9
MediaBrowser.Common.Implementations/Configuration/BaseConfigurationManager.cs

@@ -1,10 +1,13 @@
-using System.IO;
-using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Events;
 using MediaBrowser.Common.Events;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Serialization;
 using MediaBrowser.Model.Serialization;
 using System;
 using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
 using System.Threading;
 using System.Threading;
 
 
 namespace MediaBrowser.Common.Implementations.Configuration
 namespace MediaBrowser.Common.Implementations.Configuration
@@ -25,6 +28,11 @@ namespace MediaBrowser.Common.Implementations.Configuration
         /// </summary>
         /// </summary>
         public event EventHandler<EventArgs> ConfigurationUpdated;
         public event EventHandler<EventArgs> ConfigurationUpdated;
 
 
+        /// <summary>
+        /// Occurs when [named configuration updated].
+        /// </summary>
+        public event EventHandler<ConfigurationUpdateEventArgs> NamedConfigurationUpdated;
+
         /// <summary>
         /// <summary>
         /// Gets the logger.
         /// Gets the logger.
         /// </summary>
         /// </summary>
@@ -74,6 +82,9 @@ namespace MediaBrowser.Common.Implementations.Configuration
             }
             }
         }
         }
 
 
+        private ConfigurationStore[] _configurationStores = {};
+        private IConfigurationFactory[] _configurationFactories;
+
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="BaseConfigurationManager" /> class.
         /// Initializes a new instance of the <see cref="BaseConfigurationManager" /> class.
         /// </summary>
         /// </summary>
@@ -89,10 +100,14 @@ namespace MediaBrowser.Common.Implementations.Configuration
             UpdateCachePath();
             UpdateCachePath();
         }
         }
 
 
-        /// <summary>
-        /// The _save lock
-        /// </summary>
-        private readonly object _configurationSaveLock = new object();
+        public void AddParts(IEnumerable<IConfigurationFactory> factories)
+        {
+            _configurationFactories = factories.ToArray();
+
+            _configurationStores = _configurationFactories
+                .SelectMany(i => i.GetConfigurations())
+                .ToArray();
+        }
 
 
         /// <summary>
         /// <summary>
         /// Saves the configuration.
         /// Saves the configuration.
@@ -103,7 +118,7 @@ namespace MediaBrowser.Common.Implementations.Configuration
 
 
             Directory.CreateDirectory(Path.GetDirectoryName(path));
             Directory.CreateDirectory(Path.GetDirectoryName(path));
 
 
-            lock (_configurationSaveLock)
+            lock (_configurationSyncLock)
             {
             {
                 XmlSerializer.SerializeToFile(CommonConfiguration, path);
                 XmlSerializer.SerializeToFile(CommonConfiguration, path);
             }
             }
@@ -144,8 +159,8 @@ namespace MediaBrowser.Common.Implementations.Configuration
         /// </summary>
         /// </summary>
         private void UpdateCachePath()
         private void UpdateCachePath()
         {
         {
-            ((BaseApplicationPaths)CommonApplicationPaths).CachePath = string.IsNullOrEmpty(CommonConfiguration.CachePath) ? 
-                null : 
+            ((BaseApplicationPaths)CommonApplicationPaths).CachePath = string.IsNullOrEmpty(CommonConfiguration.CachePath) ?
+                null :
                 CommonConfiguration.CachePath;
                 CommonConfiguration.CachePath;
         }
         }
 
 
@@ -168,5 +183,63 @@ namespace MediaBrowser.Common.Implementations.Configuration
                 }
                 }
             }
             }
         }
         }
+
+        private readonly ConcurrentDictionary<string, object> _configurations = new ConcurrentDictionary<string, object>();
+
+        private string GetConfigurationFile(string key)
+        {
+            return Path.Combine(CommonApplicationPaths.ConfigurationDirectoryPath, key.ToLower() + ".xml");
+        }
+
+        public object GetConfiguration(string key)
+        {
+            return _configurations.GetOrAdd(key, k =>
+            {
+                var file = GetConfigurationFile(key);
+
+                var configurationType = _configurationStores
+                    .First(i => string.Equals(i.Key, key, StringComparison.OrdinalIgnoreCase))
+                    .ConfigurationType;
+
+                lock (_configurationSyncLock)
+                {
+                    return ConfigurationHelper.GetXmlConfiguration(configurationType, file, XmlSerializer);
+                }
+            });
+        }
+
+        public void SaveConfiguration(string key, object configuration)
+        {
+            var configurationType = GetConfigurationType(key);
+
+            if (configuration.GetType() != configurationType)
+            {
+                throw new ArgumentException("Expected configuration type is " + configurationType.Name);
+            }
+
+            _configurations.AddOrUpdate(key, configuration, (k, v) => configuration);
+
+            var path = GetConfigurationFile(key);
+            Directory.CreateDirectory(Path.GetDirectoryName(path));
+
+            lock (_configurationSyncLock)
+            {
+                XmlSerializer.SerializeToFile(configuration, path);
+            }
+
+            EventHelper.FireEventIfNotNull(NamedConfigurationUpdated, this, new ConfigurationUpdateEventArgs
+            {
+                Key = key,
+                NewConfiguration = configuration
+
+            }, Logger);
+        }
+
+        public Type GetConfigurationType(string key)
+        {
+            return _configurationStores
+                .First(i => string.Equals(i.Key, key, StringComparison.OrdinalIgnoreCase))
+                .ConfigurationType;
+        }
     }
     }
 }
 }

+ 18 - 0
MediaBrowser.Common/Configuration/ConfigurationUpdateEventArgs.cs

@@ -0,0 +1,18 @@
+using System;
+
+namespace MediaBrowser.Common.Configuration
+{
+    public class ConfigurationUpdateEventArgs : EventArgs
+    {
+        /// <summary>
+        /// Gets or sets the key.
+        /// </summary>
+        /// <value>The key.</value>
+        public string Key { get; set; }
+        /// <summary>
+        /// Gets or sets the new configuration.
+        /// </summary>
+        /// <value>The new configuration.</value>
+        public object NewConfiguration { get; set; }
+    }
+}

+ 17 - 0
MediaBrowser.Common/Configuration/IConfigurationFactory.cs

@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+
+namespace MediaBrowser.Common.Configuration
+{
+    public interface IConfigurationFactory
+    {
+        IEnumerable<ConfigurationStore> GetConfigurations();
+    }
+
+    public class ConfigurationStore
+    {
+        public string Key { get; set; }
+
+        public Type ConfigurationType { get; set; }
+    }
+}

+ 42 - 1
MediaBrowser.Common/Configuration/IConfigurationManager.cs

@@ -1,5 +1,6 @@
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Configuration;
 using System;
 using System;
+using System.Collections.Generic;
 
 
 namespace MediaBrowser.Common.Configuration
 namespace MediaBrowser.Common.Configuration
 {
 {
@@ -9,7 +10,12 @@ namespace MediaBrowser.Common.Configuration
         /// Occurs when [configuration updated].
         /// Occurs when [configuration updated].
         /// </summary>
         /// </summary>
         event EventHandler<EventArgs> ConfigurationUpdated;
         event EventHandler<EventArgs> ConfigurationUpdated;
-        
+
+        /// <summary>
+        /// Occurs when [named configuration updated].
+        /// </summary>
+        event EventHandler<ConfigurationUpdateEventArgs> NamedConfigurationUpdated;
+
         /// <summary>
         /// <summary>
         /// Gets or sets the application paths.
         /// Gets or sets the application paths.
         /// </summary>
         /// </summary>
@@ -32,5 +38,40 @@ namespace MediaBrowser.Common.Configuration
         /// </summary>
         /// </summary>
         /// <param name="newConfiguration">The new configuration.</param>
         /// <param name="newConfiguration">The new configuration.</param>
         void ReplaceConfiguration(BaseApplicationConfiguration newConfiguration);
         void ReplaceConfiguration(BaseApplicationConfiguration newConfiguration);
+
+        /// <summary>
+        /// Gets the configuration.
+        /// </summary>
+        /// <param name="key">The key.</param>
+        /// <returns>System.Object.</returns>
+        object GetConfiguration(string key);
+
+        /// <summary>
+        /// Gets the type of the configuration.
+        /// </summary>
+        /// <param name="key">The key.</param>
+        /// <returns>Type.</returns>
+        Type GetConfigurationType(string key);
+
+        /// <summary>
+        /// Saves the configuration.
+        /// </summary>
+        /// <param name="key">The key.</param>
+        /// <param name="configuration">The configuration.</param>
+        void SaveConfiguration(string key, object configuration);
+
+        /// <summary>
+        /// Adds the parts.
+        /// </summary>
+        /// <param name="factories">The factories.</param>
+        void AddParts(IEnumerable<IConfigurationFactory> factories);
+    }
+
+    public static class ConfigurationManagerExtensions
+    {
+        public static T GetConfiguration<T>(this IConfigurationManager manager, string key)
+        {
+            return (T)manager.GetConfiguration(key);
+        }
     }
     }
 }
 }

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

@@ -55,7 +55,9 @@
       <Link>Properties\SharedVersion.cs</Link>
       <Link>Properties\SharedVersion.cs</Link>
     </Compile>
     </Compile>
     <Compile Include="Configuration\ConfigurationHelper.cs" />
     <Compile Include="Configuration\ConfigurationHelper.cs" />
+    <Compile Include="Configuration\ConfigurationUpdateEventArgs.cs" />
     <Compile Include="Configuration\IConfigurationManager.cs" />
     <Compile Include="Configuration\IConfigurationManager.cs" />
+    <Compile Include="Configuration\IConfigurationFactory.cs" />
     <Compile Include="Constants\Constants.cs" />
     <Compile Include="Constants\Constants.cs" />
     <Compile Include="Events\EventHelper.cs" />
     <Compile Include="Events\EventHelper.cs" />
     <Compile Include="Extensions\BaseExtensions.cs" />
     <Compile Include="Extensions\BaseExtensions.cs" />

+ 5 - 0
MediaBrowser.Common/Net/MimeTypes.cs

@@ -228,6 +228,11 @@ namespace MediaBrowser.Common.Net
                 return "text/vtt";
                 return "text/vtt";
             }
             }
 
 
+            if (ext.Equals(".bif", StringComparison.OrdinalIgnoreCase))
+            {
+                return "application/octet-stream";
+            }
+
             throw new ArgumentException("Argument not supported: " + path);
             throw new ArgumentException("Argument not supported: " + path);
         }
         }
     }
     }

+ 7 - 0
MediaBrowser.Controller/Chapters/IChapterManager.cs

@@ -3,6 +3,7 @@ using MediaBrowser.Model.Chapters;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
+using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 
 
 namespace MediaBrowser.Controller.Chapters
 namespace MediaBrowser.Controller.Chapters
@@ -70,5 +71,11 @@ namespace MediaBrowser.Controller.Chapters
         /// </summary>
         /// </summary>
         /// <returns>IEnumerable{ChapterProviderInfo}.</returns>
         /// <returns>IEnumerable{ChapterProviderInfo}.</returns>
         IEnumerable<ChapterProviderInfo> GetProviders();
         IEnumerable<ChapterProviderInfo> GetProviders();
+
+        /// <summary>
+        /// Gets the configuration.
+        /// </summary>
+        /// <returns>ChapterOptions.</returns>
+        ChapterOptions GetConfiguration();
     }
     }
 }
 }

+ 21 - 0
MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs

@@ -43,6 +43,27 @@ namespace MediaBrowser.Controller.MediaEncoding
         /// <returns>Task{Stream}.</returns>
         /// <returns>Task{Stream}.</returns>
         Task<Stream> ExtractVideoImage(string[] inputFiles, MediaProtocol protocol, Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken);
         Task<Stream> ExtractVideoImage(string[] inputFiles, MediaProtocol protocol, Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken);
 
 
+        /// <summary>
+        /// Extracts the video images on interval.
+        /// </summary>
+        /// <param name="inputFiles">The input files.</param>
+        /// <param name="protocol">The protocol.</param>
+        /// <param name="threedFormat">The threed format.</param>
+        /// <param name="interval">The interval.</param>
+        /// <param name="targetDirectory">The target directory.</param>
+        /// <param name="filenamePrefix">The filename prefix.</param>
+        /// <param name="maxWidth">The maximum width.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        Task ExtractVideoImagesOnInterval(string[] inputFiles, 
+            MediaProtocol protocol, 
+            Video3DFormat? threedFormat, 
+            TimeSpan interval, 
+            string targetDirectory, 
+            string filenamePrefix, 
+            int? maxWidth,
+            CancellationToken cancellationToken);
+        
         /// <summary>
         /// <summary>
         /// Gets the media info.
         /// Gets the media info.
         /// </summary>
         /// </summary>

+ 29 - 0
MediaBrowser.Dlna/ConfigurationExtension.cs

@@ -0,0 +1,29 @@
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Model.Configuration;
+using System.Collections.Generic;
+
+namespace MediaBrowser.Dlna
+{
+    public static class ConfigurationExtension
+    {
+        public static DlnaOptions GetDlnaConfiguration(this IConfigurationManager manager)
+        {
+            return manager.GetConfiguration<DlnaOptions>("dlna");
+        }
+    }
+
+    public class DlnaConfigurationFactory : IConfigurationFactory
+    {
+        public IEnumerable<ConfigurationStore> GetConfigurations()
+        {
+            return new List<ConfigurationStore>
+            {
+                new ConfigurationStore
+                {
+                    Key = "dlna",
+                    ConfigurationType = typeof (DlnaOptions)
+                }
+            };
+        }
+    }
+}

+ 4 - 2
MediaBrowser.Dlna/ContentDirectory/ContentDirectory.cs

@@ -89,9 +89,11 @@ namespace MediaBrowser.Dlna.ContentDirectory
                 }
                 }
             }
             }
 
 
-            if (!string.IsNullOrEmpty(_config.Configuration.DlnaOptions.DefaultUserId))
+            var userId = _config.GetDlnaConfiguration().DefaultUserId;
+
+            if (!string.IsNullOrEmpty(userId))
             {
             {
-                var user = _userManager.GetUserById(new Guid(_config.Configuration.DlnaOptions.DefaultUserId));
+                var user = _userManager.GetUserById(new Guid(userId));
 
 
                 if (user != null)
                 if (user != null)
                 {
                 {

+ 14 - 8
MediaBrowser.Dlna/Main/DlnaEntryPoint.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
@@ -58,34 +59,39 @@ namespace MediaBrowser.Dlna.Main
             StartSsdpHandler();
             StartSsdpHandler();
             ReloadComponents();
             ReloadComponents();
 
 
-            _config.ConfigurationUpdated += ConfigurationUpdated;
+            _config.NamedConfigurationUpdated += _config_NamedConfigurationUpdated;
         }
         }
 
 
-        void ConfigurationUpdated(object sender, EventArgs e)
+        void _config_NamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e)
         {
         {
-            ReloadComponents();
+            if (string.Equals(e.Key, "dlna", StringComparison.OrdinalIgnoreCase))
+            {
+                ReloadComponents();
+            }
         }
         }
 
 
         private void ReloadComponents()
         private void ReloadComponents()
         {
         {
             var isServerStarted = _dlnaServerStarted;
             var isServerStarted = _dlnaServerStarted;
 
 
-            if (_config.Configuration.DlnaOptions.EnableServer && !isServerStarted)
+            var options = _config.GetDlnaConfiguration();
+
+            if (options.EnableServer && !isServerStarted)
             {
             {
                 StartDlnaServer();
                 StartDlnaServer();
             }
             }
-            else if (!_config.Configuration.DlnaOptions.EnableServer && isServerStarted)
+            else if (!options.EnableServer && isServerStarted)
             {
             {
                 DisposeDlnaServer();
                 DisposeDlnaServer();
             }
             }
 
 
             var isPlayToStarted = _manager != null;
             var isPlayToStarted = _manager != null;
 
 
-            if (_config.Configuration.DlnaOptions.EnablePlayTo && !isPlayToStarted)
+            if (options.EnablePlayTo && !isPlayToStarted)
             {
             {
                 StartPlayToManager();
                 StartPlayToManager();
             }
             }
-            else if (!_config.Configuration.DlnaOptions.EnablePlayTo && isPlayToStarted)
+            else if (!options.EnablePlayTo && isPlayToStarted)
             {
             {
                 DisposePlayToManager();
                 DisposePlayToManager();
             }
             }

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

@@ -51,6 +51,7 @@
     <Compile Include="..\SharedVersion.cs">
     <Compile Include="..\SharedVersion.cs">
       <Link>Properties\SharedVersion.cs</Link>
       <Link>Properties\SharedVersion.cs</Link>
     </Compile>
     </Compile>
+    <Compile Include="ConfigurationExtension.cs" />
     <Compile Include="ConnectionManager\ConnectionManager.cs" />
     <Compile Include="ConnectionManager\ConnectionManager.cs" />
     <Compile Include="ConnectionManager\ConnectionManagerXmlBuilder.cs" />
     <Compile Include="ConnectionManager\ConnectionManagerXmlBuilder.cs" />
     <Compile Include="ConnectionManager\ControlHandler.cs" />
     <Compile Include="ConnectionManager\ControlHandler.cs" />

+ 3 - 3
MediaBrowser.Dlna/PlayTo/PlayToManager.cs

@@ -181,7 +181,7 @@ namespace MediaBrowser.Dlna.PlayTo
                 return;
                 return;
             }
             }
 
 
-            if (_config.Configuration.DlnaOptions.EnableDebugLogging)
+            if (_config.GetDlnaConfiguration().EnableDebugLogging)
             {
             {
                 var headerTexts = args.Headers.Select(i => string.Format("{0}={1}", i.Key, i.Value));
                 var headerTexts = args.Headers.Select(i => string.Format("{0}={1}", i.Key, i.Value));
                 var headerText = string.Join(",", headerTexts.ToArray());
                 var headerText = string.Join(",", headerTexts.ToArray());
@@ -220,7 +220,7 @@ namespace MediaBrowser.Dlna.PlayTo
                     {
                     {
                         _ssdpHandler.SendRendererSearchMessage(new IPEndPoint(localIp, 1900));
                         _ssdpHandler.SendRendererSearchMessage(new IPEndPoint(localIp, 1900));
 
 
-                        var delay = _config.Configuration.DlnaOptions.ClientDiscoveryIntervalSeconds * 1000;
+                        var delay = _config.GetDlnaConfiguration().ClientDiscoveryIntervalSeconds * 1000;
 
 
                         await Task.Delay(delay, _tokenSource.Token).ConfigureAwait(false);
                         await Task.Delay(delay, _tokenSource.Token).ConfigureAwait(false);
                     }
                     }
@@ -250,7 +250,7 @@ namespace MediaBrowser.Dlna.PlayTo
                     {
                     {
                         socket.SendTo(request, new IPEndPoint(IPAddress.Parse("239.255.255.250"), 1900));
                         socket.SendTo(request, new IPEndPoint(IPAddress.Parse("239.255.255.250"), 1900));
 
 
-                        var delay = _config.Configuration.DlnaOptions.ClientDiscoveryIntervalSeconds * 1000;
+                        var delay = _config.GetDlnaConfiguration().ClientDiscoveryIntervalSeconds * 1000;
 
 
                         await Task.Delay(delay).ConfigureAwait(false);
                         await Task.Delay(delay).ConfigureAwait(false);
                     }
                     }

+ 3 - 3
MediaBrowser.Dlna/PlayTo/SsdpHttpClient.cs

@@ -58,7 +58,7 @@ namespace MediaBrowser.Dlna.PlayTo
             {
             {
                 Url = url,
                 Url = url,
                 UserAgent = USERAGENT,
                 UserAgent = USERAGENT,
-                LogRequest = _config.Configuration.DlnaOptions.EnableDebugLogging,
+                LogRequest = _config.GetDlnaConfiguration().EnableDebugLogging,
                 LogErrorResponseBody = true
                 LogErrorResponseBody = true
             };
             };
 
 
@@ -76,7 +76,7 @@ namespace MediaBrowser.Dlna.PlayTo
             {
             {
                 Url = url,
                 Url = url,
                 UserAgent = USERAGENT,
                 UserAgent = USERAGENT,
-                LogRequest = _config.Configuration.DlnaOptions.EnableDebugLogging,
+                LogRequest = _config.GetDlnaConfiguration().EnableDebugLogging,
                 LogErrorResponseBody = true
                 LogErrorResponseBody = true
             };
             };
 
 
@@ -103,7 +103,7 @@ namespace MediaBrowser.Dlna.PlayTo
             {
             {
                 Url = url,
                 Url = url,
                 UserAgent = USERAGENT,
                 UserAgent = USERAGENT,
-                LogRequest = _config.Configuration.DlnaOptions.EnableDebugLogging,
+                LogRequest = _config.GetDlnaConfiguration().EnableDebugLogging,
                 LogErrorResponseBody = true
                 LogErrorResponseBody = true
             };
             };
 
 

+ 1 - 1
MediaBrowser.Dlna/Service/BaseControlHandler.cs

@@ -28,7 +28,7 @@ namespace MediaBrowser.Dlna.Service
         {
         {
             try
             try
             {
             {
-                if (Config.Configuration.DlnaOptions.EnableDebugLogging)
+                if (Config.GetDlnaConfiguration().EnableDebugLogging)
                 {
                 {
                     LogRequest(request);
                     LogRequest(request);
                 }
                 }

+ 17 - 13
MediaBrowser.Dlna/Ssdp/SsdpHandler.cs

@@ -1,4 +1,4 @@
-using System.Text;
+using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Events;
 using MediaBrowser.Common.Events;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Dlna.Server;
 using MediaBrowser.Dlna.Server;
@@ -9,6 +9,7 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
 using System.Net;
 using System.Net;
 using System.Net.Sockets;
 using System.Net.Sockets;
+using System.Text;
 using System.Threading;
 using System.Threading;
 
 
 namespace MediaBrowser.Dlna.Ssdp
 namespace MediaBrowser.Dlna.Ssdp
@@ -42,12 +43,15 @@ namespace MediaBrowser.Dlna.Ssdp
             _config = config;
             _config = config;
             _serverSignature = serverSignature;
             _serverSignature = serverSignature;
 
 
-            _config.ConfigurationUpdated += _config_ConfigurationUpdated;
+            _config.NamedConfigurationUpdated += _config_ConfigurationUpdated;
         }
         }
 
 
-        void _config_ConfigurationUpdated(object sender, EventArgs e)
+        void _config_ConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e)
         {
         {
-            ReloadAliveNotifier();
+            if (string.Equals(e.Key, "dlna", StringComparison.OrdinalIgnoreCase))
+            {
+                ReloadAliveNotifier();
+            }
         }
         }
 
 
         public event EventHandler<SsdpMessageEventArgs> MessageReceived;
         public event EventHandler<SsdpMessageEventArgs> MessageReceived;
@@ -142,7 +146,7 @@ namespace MediaBrowser.Dlna.Ssdp
 
 
         private void RespondToSearch(IPEndPoint endpoint, string deviceType)
         private void RespondToSearch(IPEndPoint endpoint, string deviceType)
         {
         {
-            if (_config.Configuration.DlnaOptions.EnableDebugLogging)
+            if (_config.GetDlnaConfiguration().EnableDebugLogging)
             {
             {
                 _logger.Debug("RespondToSearch");
                 _logger.Debug("RespondToSearch");
             }
             }
@@ -166,7 +170,7 @@ namespace MediaBrowser.Dlna.Ssdp
 
 
                     SendDatagram(header, values, endpoint, null);
                     SendDatagram(header, values, endpoint, null);
 
 
-                    if (_config.Configuration.DlnaOptions.EnableDebugLogging)
+                    if (_config.GetDlnaConfiguration().EnableDebugLogging)
                     {
                     {
                         _logger.Debug("{1} - Responded to a {0} request to {2}", d.Type, endpoint, d.Address.ToString());
                         _logger.Debug("{1} - Responded to a {0} request to {2}", d.Type, endpoint, d.Address.ToString());
                     }
                     }
@@ -255,14 +259,14 @@ namespace MediaBrowser.Dlna.Ssdp
 
 
                 var received = (byte[])result.AsyncState;
                 var received = (byte[])result.AsyncState;
 
 
-                if (_config.Configuration.DlnaOptions.EnableDebugLogging)
+                if (_config.GetDlnaConfiguration().EnableDebugLogging)
                 {
                 {
                     _logger.Debug(Encoding.ASCII.GetString(received));
                     _logger.Debug(Encoding.ASCII.GetString(received));
                 }
                 }
 
 
                 var args = SsdpHelper.ParseSsdpResponse(received, (IPEndPoint)endpoint);
                 var args = SsdpHelper.ParseSsdpResponse(received, (IPEndPoint)endpoint);
 
 
-                if (_config.Configuration.DlnaOptions.EnableDebugLogging)
+                if (_config.GetDlnaConfiguration().EnableDebugLogging)
                 {
                 {
                     var headerTexts = args.Headers.Select(i => string.Format("{0}={1}", i.Key, i.Value));
                     var headerTexts = args.Headers.Select(i => string.Format("{0}={1}", i.Key, i.Value));
                     var headerText = string.Join(",", headerTexts.ToArray());
                     var headerText = string.Join(",", headerTexts.ToArray());
@@ -285,7 +289,7 @@ namespace MediaBrowser.Dlna.Ssdp
 
 
         public void Dispose()
         public void Dispose()
         {
         {
-            _config.ConfigurationUpdated -= _config_ConfigurationUpdated;
+            _config.NamedConfigurationUpdated -= _config_ConfigurationUpdated;
 
 
             _isDisposed = true;
             _isDisposed = true;
             while (_messageQueue.Count != 0)
             while (_messageQueue.Count != 0)
@@ -337,7 +341,7 @@ namespace MediaBrowser.Dlna.Ssdp
 
 
         private void NotifyAll()
         private void NotifyAll()
         {
         {
-            if (_config.Configuration.DlnaOptions.EnableDebugLogging)
+            if (_config.GetDlnaConfiguration().EnableDebugLogging)
             {
             {
                 _logger.Debug("Sending alive notifications");
                 _logger.Debug("Sending alive notifications");
             }
             }
@@ -362,7 +366,7 @@ namespace MediaBrowser.Dlna.Ssdp
             values["NT"] = dev.Type;
             values["NT"] = dev.Type;
             values["USN"] = dev.USN;
             values["USN"] = dev.USN;
 
 
-            if (_config.Configuration.DlnaOptions.EnableDebugLogging)
+            if (_config.GetDlnaConfiguration().EnableDebugLogging)
             {
             {
                 _logger.Debug("{0} said {1}", dev.USN, type);
                 _logger.Debug("{0} said {1}", dev.USN, type);
             }
             }
@@ -406,13 +410,13 @@ namespace MediaBrowser.Dlna.Ssdp
         private int _aliveNotifierIntervalMs;
         private int _aliveNotifierIntervalMs;
         private void ReloadAliveNotifier()
         private void ReloadAliveNotifier()
         {
         {
-            if (!_config.Configuration.DlnaOptions.BlastAliveMessages)
+            if (!_config.GetDlnaConfiguration().BlastAliveMessages)
             {
             {
                 DisposeNotificationTimer();
                 DisposeNotificationTimer();
                 return;
                 return;
             }
             }
 
 
-            var intervalMs = _config.Configuration.DlnaOptions.BlastAliveMessageIntervalSeconds * 1000;
+            var intervalMs = _config.GetDlnaConfiguration().BlastAliveMessageIntervalSeconds * 1000;
 
 
             if (_notificationTimer == null || _aliveNotifierIntervalMs != intervalMs)
             if (_notificationTimer == null || _aliveNotifierIntervalMs != intervalMs)
             {
             {

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

@@ -152,7 +152,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
                     RedirectStandardError = true,
                     RedirectStandardError = true,
                     FileName = FFProbePath,
                     FileName = FFProbePath,
                     Arguments = string.Format(args,
                     Arguments = string.Format(args,
-                        probeSizeArgument, inputPath).Trim(),
+                    probeSizeArgument, inputPath).Trim(),
 
 
                     WindowStyle = ProcessWindowStyle.Hidden,
                     WindowStyle = ProcessWindowStyle.Hidden,
                     ErrorDialog = false
                     ErrorDialog = false
@@ -186,8 +186,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
             {
             {
                 process.BeginErrorReadLine();
                 process.BeginErrorReadLine();
 
 
-                result =
-                    _jsonSerializer.DeserializeFromStream<InternalMediaInfoResult>(process.StandardOutput.BaseStream);
+                result = _jsonSerializer.DeserializeFromStream<InternalMediaInfoResult>(process.StandardOutput.BaseStream);
             }
             }
             catch
             catch
             {
             {
@@ -292,7 +291,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
             // apply some filters to thumbnail extracted below (below) crop any black lines that we made and get the correct ar then scale to width 600. 
             // apply some filters to thumbnail extracted below (below) crop any black lines that we made and get the correct ar then scale to width 600. 
             // This filter chain may have adverse effects on recorded tv thumbnails if ar changes during presentation ex. commercials @ diff ar
             // This filter chain may have adverse effects on recorded tv thumbnails if ar changes during presentation ex. commercials @ diff ar
             var vf = "scale=600:trunc(600/dar/2)*2";
             var vf = "scale=600:trunc(600/dar/2)*2";
-            //crop=min(iw\,ih*dar):min(ih\,iw/dar):(iw-min(iw\,iw*sar))/2:(ih - min (ih\,ih/sar))/2,scale=600:(600/dar),thumbnail" -f image2
 
 
             if (threedFormat.HasValue)
             if (threedFormat.HasValue)
             {
             {
@@ -344,7 +342,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
                     WindowStyle = ProcessWindowStyle.Hidden,
                     WindowStyle = ProcessWindowStyle.Hidden,
                     ErrorDialog = false,
                     ErrorDialog = false,
                     RedirectStandardOutput = true,
                     RedirectStandardOutput = true,
-                    RedirectStandardError = true
+                    RedirectStandardError = true,
+                    RedirectStandardInput = true
                 }
                 }
             };
             };
 
 
@@ -370,7 +369,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
                 {
                 {
                     _logger.Info("Killing ffmpeg process");
                     _logger.Info("Killing ffmpeg process");
 
 
-                    process.Kill();
+                    process.StandardInput.WriteLine("q");
 
 
                     process.WaitForExit(1000);
                     process.WaitForExit(1000);
                 }
                 }
@@ -437,5 +436,93 @@ namespace MediaBrowser.MediaEncoding.Encoder
         {
         {
             return time.ToString(@"hh\:mm\:ss\.fff", UsCulture);
             return time.ToString(@"hh\:mm\:ss\.fff", UsCulture);
         }
         }
+
+        public async Task ExtractVideoImagesOnInterval(string[] inputFiles,
+            MediaProtocol protocol,
+            Video3DFormat? threedFormat,
+            TimeSpan interval,
+            string targetDirectory,
+            string filenamePrefix,
+            int? maxWidth,
+            CancellationToken cancellationToken)
+        {
+            var resourcePool = _videoImageResourcePool;
+
+            var inputArgument = GetInputArgument(inputFiles, protocol);
+
+            var vf = "fps=fps=1/" + interval.TotalSeconds.ToString(UsCulture);
+
+            if (maxWidth.HasValue)
+            {
+                var maxWidthParam = maxWidth.Value.ToString(UsCulture);
+
+                vf += string.Format(",scale=min(iw\\,{0}):trunc(ow/dar/2)*2", maxWidthParam);
+            }
+
+            Directory.CreateDirectory(targetDirectory);
+            var outputPath = Path.Combine(targetDirectory, filenamePrefix + "%05d.jpg");
+
+            var args = string.Format("-i {0} -threads 0 -v quiet -vf \"{2}\" -f image2 \"{1}\"", inputArgument, outputPath, vf);
+
+            var probeSize = GetProbeSizeArgument(new[] { inputArgument }, protocol);
+
+            if (!string.IsNullOrEmpty(probeSize))
+            {
+                args = probeSize + " " + args;
+            }
+
+            var process = new Process
+            {
+                StartInfo = new ProcessStartInfo
+                {
+                    CreateNoWindow = true,
+                    UseShellExecute = false,
+                    FileName = FFMpegPath,
+                    Arguments = args,
+                    WindowStyle = ProcessWindowStyle.Hidden,
+                    ErrorDialog = false,
+                    RedirectStandardInput = true
+                }
+            };
+
+            _logger.Info(process.StartInfo.FileName + " " + process.StartInfo.Arguments);
+            
+            await resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
+
+            process.Start();
+
+            var ranToCompletion = process.WaitForExit(120000);
+
+            if (!ranToCompletion)
+            {
+                try
+                {
+                    _logger.Info("Killing ffmpeg process");
+
+                    process.StandardInput.WriteLine("q");
+
+                    process.WaitForExit(1000);
+                }
+                catch (Exception ex)
+                {
+                    _logger.ErrorException("Error killing process", ex);
+                }
+            }
+
+            resourcePool.Release();
+
+            var exitCode = ranToCompletion ? process.ExitCode : -1;
+
+            process.Dispose();
+
+            if (exitCode == -1)
+            {
+                var msg = string.Format("ffmpeg image extraction failed for {0}", inputArgument);
+
+                _logger.Error(msg);
+
+                throw new ApplicationException(msg);
+            }
+        }
     }
     }
 }
 }

+ 5 - 6
MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs

@@ -342,12 +342,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles
                 {
                 {
                     RedirectStandardOutput = false,
                     RedirectStandardOutput = false,
                     RedirectStandardError = true,
                     RedirectStandardError = true,
+                    RedirectStandardInput = true,
 
 
                     CreateNoWindow = true,
                     CreateNoWindow = true,
                     UseShellExecute = false,
                     UseShellExecute = false,
                     FileName = _mediaEncoder.EncoderPath,
                     FileName = _mediaEncoder.EncoderPath,
-                    Arguments =
-                        string.Format("{0} -i \"{1}\" -c:s ass \"{2}\"", encodingParam, inputPath, outputPath),
+                    Arguments = string.Format("{0} -i \"{1}\" -c:s ass \"{2}\"", encodingParam, inputPath, outputPath),
 
 
                     WindowStyle = ProcessWindowStyle.Hidden,
                     WindowStyle = ProcessWindowStyle.Hidden,
                     ErrorDialog = false
                     ErrorDialog = false
@@ -385,8 +385,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
                 {
                 {
                     _logger.Info("Killing ffmpeg subtitle conversion process");
                     _logger.Info("Killing ffmpeg subtitle conversion process");
 
 
-                    process.Kill();
-
+                    process.StandardInput.WriteLine("q");
                     process.WaitForExit(1000);
                     process.WaitForExit(1000);
 
 
                     await logTask.ConfigureAwait(false);
                     await logTask.ConfigureAwait(false);
@@ -520,6 +519,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
 
 
                     RedirectStandardOutput = false,
                     RedirectStandardOutput = false,
                     RedirectStandardError = true,
                     RedirectStandardError = true,
+                    RedirectStandardInput = true,
 
 
                     FileName = _mediaEncoder.EncoderPath,
                     FileName = _mediaEncoder.EncoderPath,
                     Arguments = processArgs,
                     Arguments = processArgs,
@@ -559,8 +559,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
                 {
                 {
                     _logger.Info("Killing ffmpeg subtitle extraction process");
                     _logger.Info("Killing ffmpeg subtitle extraction process");
 
 
-                    process.Kill();
-
+                    process.StandardInput.WriteLine("q");
                     process.WaitForExit(1000);
                     process.WaitForExit(1000);
                 }
                 }
                 catch (Exception ex)
                 catch (Exception ex)

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

@@ -104,6 +104,12 @@
     <Compile Include="..\MediaBrowser.Model\Configuration\BaseApplicationConfiguration.cs">
     <Compile Include="..\MediaBrowser.Model\Configuration\BaseApplicationConfiguration.cs">
       <Link>Configuration\BaseApplicationConfiguration.cs</Link>
       <Link>Configuration\BaseApplicationConfiguration.cs</Link>
     </Compile>
     </Compile>
+    <Compile Include="..\MediaBrowser.Model\Configuration\ChannelOptions.cs">
+      <Link>Configuration\ChannelOptions.cs</Link>
+    </Compile>
+    <Compile Include="..\MediaBrowser.Model\Configuration\ChapterOptions.cs">
+      <Link>Configuration\ChapterOptions.cs</Link>
+    </Compile>
     <Compile Include="..\MediaBrowser.Model\Configuration\DlnaOptions.cs">
     <Compile Include="..\MediaBrowser.Model\Configuration\DlnaOptions.cs">
       <Link>Configuration\DlnaOptions.cs</Link>
       <Link>Configuration\DlnaOptions.cs</Link>
     </Compile>
     </Compile>
@@ -152,6 +158,9 @@
     <Compile Include="..\MediaBrowser.Model\Configuration\SubtitleOptions.cs">
     <Compile Include="..\MediaBrowser.Model\Configuration\SubtitleOptions.cs">
       <Link>Configuration\SubtitleOptions.cs</Link>
       <Link>Configuration\SubtitleOptions.cs</Link>
     </Compile>
     </Compile>
+    <Compile Include="..\MediaBrowser.Model\Configuration\SubtitlePlaybackMode.cs">
+      <Link>Configuration\SubtitlePlaybackMode.cs</Link>
+    </Compile>
     <Compile Include="..\MediaBrowser.Model\Configuration\TvFileOrganizationOptions.cs">
     <Compile Include="..\MediaBrowser.Model\Configuration\TvFileOrganizationOptions.cs">
       <Link>Configuration\TvFileOrganizationOptions.cs</Link>
       <Link>Configuration\TvFileOrganizationOptions.cs</Link>
     </Compile>
     </Compile>

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

@@ -91,6 +91,12 @@
     <Compile Include="..\MediaBrowser.Model\Configuration\BaseApplicationConfiguration.cs">
     <Compile Include="..\MediaBrowser.Model\Configuration\BaseApplicationConfiguration.cs">
       <Link>Configuration\BaseApplicationConfiguration.cs</Link>
       <Link>Configuration\BaseApplicationConfiguration.cs</Link>
     </Compile>
     </Compile>
+    <Compile Include="..\MediaBrowser.Model\Configuration\ChannelOptions.cs">
+      <Link>Configuration\ChannelOptions.cs</Link>
+    </Compile>
+    <Compile Include="..\MediaBrowser.Model\Configuration\ChapterOptions.cs">
+      <Link>Configuration\ChapterOptions.cs</Link>
+    </Compile>
     <Compile Include="..\MediaBrowser.Model\Configuration\DlnaOptions.cs">
     <Compile Include="..\MediaBrowser.Model\Configuration\DlnaOptions.cs">
       <Link>Configuration\DlnaOptions.cs</Link>
       <Link>Configuration\DlnaOptions.cs</Link>
     </Compile>
     </Compile>
@@ -139,6 +145,9 @@
     <Compile Include="..\MediaBrowser.Model\Configuration\SubtitleOptions.cs">
     <Compile Include="..\MediaBrowser.Model\Configuration\SubtitleOptions.cs">
       <Link>Configuration\SubtitleOptions.cs</Link>
       <Link>Configuration\SubtitleOptions.cs</Link>
     </Compile>
     </Compile>
+    <Compile Include="..\MediaBrowser.Model\Configuration\SubtitlePlaybackMode.cs">
+      <Link>Configuration\SubtitlePlaybackMode.cs</Link>
+    </Compile>
     <Compile Include="..\MediaBrowser.Model\Configuration\TvFileOrganizationOptions.cs">
     <Compile Include="..\MediaBrowser.Model\Configuration\TvFileOrganizationOptions.cs">
       <Link>Configuration\TvFileOrganizationOptions.cs</Link>
       <Link>Configuration\TvFileOrganizationOptions.cs</Link>
     </Compile>
     </Compile>

+ 18 - 0
MediaBrowser.Model/Configuration/ChannelOptions.cs

@@ -0,0 +1,18 @@
+namespace MediaBrowser.Model.Configuration
+{
+    public class ChannelOptions
+    {
+        public int? PreferredStreamingWidth { get; set; }
+
+        public string DownloadPath { get; set; }
+        public int? MaxDownloadAge { get; set; }
+
+        public string[] DownloadingChannels { get; set; }
+
+        public ChannelOptions()
+        {
+            DownloadingChannels = new string[] { };
+            MaxDownloadAge = 30;
+        }
+    }
+}

+ 27 - 0
MediaBrowser.Model/Configuration/ChapterOptions.cs

@@ -0,0 +1,27 @@
+namespace MediaBrowser.Model.Configuration
+{
+    public class ChapterOptions
+    {
+        public bool EnableMovieChapterImageExtraction { get; set; }
+        public bool EnableEpisodeChapterImageExtraction { get; set; }
+        public bool EnableOtherVideoChapterImageExtraction { get; set; }
+
+        public bool DownloadMovieChapters { get; set; }
+        public bool DownloadEpisodeChapters { get; set; }
+
+        public string[] FetcherOrder { get; set; }
+        public string[] DisabledFetchers { get; set; }
+
+        public ChapterOptions()
+        {
+            EnableMovieChapterImageExtraction = true;
+            EnableEpisodeChapterImageExtraction = false;
+            EnableOtherVideoChapterImageExtraction = false;
+
+            DownloadMovieChapters = true;
+
+            DisabledFetchers = new string[] { };
+            FetcherOrder = new string[] { };
+        }
+    }
+}

+ 9 - 52
MediaBrowser.Model/Configuration/ServerConfiguration.cs

@@ -211,6 +211,7 @@ namespace MediaBrowser.Model.Configuration
 
 
         public string UICulture { get; set; }
         public string UICulture { get; set; }
 
 
+        [Obsolete]
         public DlnaOptions DlnaOptions { get; set; }
         public DlnaOptions DlnaOptions { get; set; }
 
 
         public double DownMixAudioBoost { get; set; }
         public double DownMixAudioBoost { get; set; }
@@ -223,6 +224,8 @@ namespace MediaBrowser.Model.Configuration
         public string[] ManualLoginClients { get; set; }
         public string[] ManualLoginClients { get; set; }
 
 
         public ChannelOptions ChannelOptions { get; set; }
         public ChannelOptions ChannelOptions { get; set; }
+
+        [Obsolete]
         public ChapterOptions ChapterOptions { get; set; }
         public ChapterOptions ChapterOptions { get; set; }
 
 
         /// <summary>
         /// <summary>
@@ -268,73 +271,27 @@ namespace MediaBrowser.Model.Configuration
 
 
             SeasonZeroDisplayName = "Specials";
             SeasonZeroDisplayName = "Specials";
 
 
-            LiveTvOptions = new LiveTvOptions();
-
-            TvFileOrganizationOptions = new TvFileOrganizationOptions();
-
             EnableRealtimeMonitor = true;
             EnableRealtimeMonitor = true;
 
 
-            List<MetadataOptions> options = new List<MetadataOptions>
+            UICulture = "en-us";
+
+            MetadataOptions = new List<MetadataOptions>
             {
             {
                 new MetadataOptions(1, 1280) {ItemType = "Book"},
                 new MetadataOptions(1, 1280) {ItemType = "Book"},
                 new MetadataOptions(1, 1280) {ItemType = "MusicAlbum"},
                 new MetadataOptions(1, 1280) {ItemType = "MusicAlbum"},
                 new MetadataOptions(1, 1280) {ItemType = "MusicArtist"},
                 new MetadataOptions(1, 1280) {ItemType = "MusicArtist"},
                 new MetadataOptions(0, 1280) {ItemType = "Season"}
                 new MetadataOptions(0, 1280) {ItemType = "Season"}
-            };
-
-            MetadataOptions = options.ToArray();
 
 
-            DlnaOptions = new DlnaOptions();
-
-            UICulture = "en-us";
+            }.ToArray();
 
 
             NotificationOptions = new NotificationOptions();
             NotificationOptions = new NotificationOptions();
 
 
             SubtitleOptions = new SubtitleOptions();
             SubtitleOptions = new SubtitleOptions();
 
 
             ChannelOptions = new ChannelOptions();
             ChannelOptions = new ChannelOptions();
-            ChapterOptions = new ChapterOptions();
-        }
-    }
-
-    public class ChannelOptions
-    {
-        public int? PreferredStreamingWidth { get; set; }
 
 
-        public string DownloadPath { get; set; }
-        public int? MaxDownloadAge { get; set; }
-
-        public string[] DownloadingChannels { get; set; }
-
-        public ChannelOptions()
-        {
-            DownloadingChannels = new string[] { };
-            MaxDownloadAge = 30;
-        }
-    }
-
-    public class ChapterOptions
-    {
-        public bool EnableMovieChapterImageExtraction { get; set; }
-        public bool EnableEpisodeChapterImageExtraction { get; set; }
-        public bool EnableOtherVideoChapterImageExtraction { get; set; }
-
-        public bool DownloadMovieChapters { get; set; }
-        public bool DownloadEpisodeChapters { get; set; }
-
-        public string[] FetcherOrder { get; set; }
-        public string[] DisabledFetchers { get; set; }
-
-        public ChapterOptions()
-        {
-            EnableMovieChapterImageExtraction = true;
-            EnableEpisodeChapterImageExtraction = false;
-            EnableOtherVideoChapterImageExtraction = false;
-
-            DownloadMovieChapters = true;
-
-            DisabledFetchers = new string[] { };
-            FetcherOrder = new string[] { };
+            LiveTvOptions = new LiveTvOptions();
+            TvFileOrganizationOptions = new TvFileOrganizationOptions();
         }
         }
     }
     }
 }
 }

+ 10 - 0
MediaBrowser.Model/Configuration/SubtitlePlaybackMode.cs

@@ -0,0 +1,10 @@
+namespace MediaBrowser.Model.Configuration
+{
+    public enum SubtitlePlaybackMode
+    {
+        Default = 0,
+        Always = 1,
+        OnlyForced = 2,
+        None = 3
+    }
+}

+ 0 - 8
MediaBrowser.Model/Configuration/UserConfiguration.cs

@@ -91,12 +91,4 @@ namespace MediaBrowser.Model.Configuration
             ExcludeFoldersFromGrouping = new string[] { };
             ExcludeFoldersFromGrouping = new string[] { };
         }
         }
     }
     }
-
-    public enum SubtitlePlaybackMode
-    {
-        Default = 0,
-        Always = 1,
-        OnlyForced = 2,
-        None = 3
-    }
 }
 }

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

@@ -73,6 +73,9 @@
     <Compile Include="Channels\ChannelQuery.cs" />
     <Compile Include="Channels\ChannelQuery.cs" />
     <Compile Include="Chapters\RemoteChapterInfo.cs" />
     <Compile Include="Chapters\RemoteChapterInfo.cs" />
     <Compile Include="Chapters\RemoteChapterResult.cs" />
     <Compile Include="Chapters\RemoteChapterResult.cs" />
+    <Compile Include="Configuration\ChannelOptions.cs" />
+    <Compile Include="Configuration\ChapterOptions.cs" />
+    <Compile Include="Configuration\SubtitlePlaybackMode.cs" />
     <Compile Include="Configuration\TvFileOrganizationOptions.cs" />
     <Compile Include="Configuration\TvFileOrganizationOptions.cs" />
     <Compile Include="Configuration\BaseApplicationConfiguration.cs" />
     <Compile Include="Configuration\BaseApplicationConfiguration.cs" />
     <Compile Include="Configuration\DlnaOptions.cs" />
     <Compile Include="Configuration\DlnaOptions.cs" />

+ 29 - 3
MediaBrowser.Providers/Chapters/ChapterManager.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Extensions;
 using MediaBrowser.Controller.Chapters;
 using MediaBrowser.Controller.Chapters;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
@@ -8,6 +9,7 @@ using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Chapters;
 using MediaBrowser.Model.Chapters;
+using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
 using System;
 using System;
@@ -192,8 +194,10 @@ namespace MediaBrowser.Providers.Chapters
 
 
             if (!includeDisabledProviders)
             if (!includeDisabledProviders)
             {
             {
+                var options = GetConfiguration();
+
                 providers = providers
                 providers = providers
-                    .Where(i => !_config.Configuration.ChapterOptions.DisabledFetchers.Contains(i.Name))
+                    .Where(i => !options.DisabledFetchers.Contains(i.Name))
                     .ToArray();
                     .ToArray();
             }
             }
 
 
@@ -224,8 +228,10 @@ namespace MediaBrowser.Providers.Chapters
 
 
         private int GetConfiguredOrder(IChapterProvider provider)
         private int GetConfiguredOrder(IChapterProvider provider)
         {
         {
+            var options = GetConfiguration();
+            
             // See if there's a user-defined order
             // See if there's a user-defined order
-            var index = Array.IndexOf(_config.Configuration.ChapterOptions.FetcherOrder, provider.Name);
+            var index = Array.IndexOf(options.FetcherOrder, provider.Name);
 
 
             if (index != -1)
             if (index != -1)
             {
             {
@@ -257,5 +263,25 @@ namespace MediaBrowser.Providers.Chapters
         {
         {
             return _itemRepo.SaveChapters(new Guid(itemId), chapters, cancellationToken);
             return _itemRepo.SaveChapters(new Guid(itemId), chapters, cancellationToken);
         }
         }
+
+        public ChapterOptions GetConfiguration()
+        {
+            return _config.GetConfiguration<ChapterOptions>("chapters");
+        }
+    }
+
+    public class ChapterConfigurationStore : IConfigurationFactory
+    {
+        public IEnumerable<ConfigurationStore> GetConfigurations()
+        {
+            return new List<ConfigurationStore>
+            {
+                new ConfigurationStore
+                {
+                    Key = "chapters",
+                    ConfigurationType = typeof (ChapterOptions)
+                }
+            };
+        }
     }
     }
 }
 }

+ 4 - 2
MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs

@@ -492,9 +492,11 @@ namespace MediaBrowser.Providers.MediaInfo
 
 
         private async Task<List<ChapterInfo>> DownloadChapters(Video video, List<ChapterInfo> currentChapters, CancellationToken cancellationToken)
         private async Task<List<ChapterInfo>> DownloadChapters(Video video, List<ChapterInfo> currentChapters, CancellationToken cancellationToken)
         {
         {
-            if ((_config.Configuration.ChapterOptions.DownloadEpisodeChapters &&
+            var options = _chapterManager.GetConfiguration();
+
+            if ((options.DownloadEpisodeChapters &&
                  video is Episode) ||
                  video is Episode) ||
-                (_config.Configuration.ChapterOptions.DownloadMovieChapters &&
+                (options.DownloadMovieChapters &&
                  video is Movie))
                  video is Movie))
             {
             {
                 var results = await _chapterManager.Search(video, cancellationToken).ConfigureAwait(false);
                 var results = await _chapterManager.Search(video, cancellationToken).ConfigureAwait(false);

+ 9 - 5
MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs

@@ -1,12 +1,15 @@
-using MediaBrowser.Common.IO;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.IO;
 using MediaBrowser.Controller.Chapters;
 using MediaBrowser.Controller.Chapters;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.MediaEncoding;
 using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.MediaInfo;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Globalization;
 using System.Globalization;
@@ -14,7 +17,6 @@ using System.IO;
 using System.Linq;
 using System.Linq;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
-using MediaBrowser.Model.MediaInfo;
 
 
 namespace MediaBrowser.Server.Implementations.MediaEncoder
 namespace MediaBrowser.Server.Implementations.MediaEncoder
 {
 {
@@ -61,23 +63,25 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
                 return false;
                 return false;
             }
             }
 
 
+            var options = _chapterManager.GetConfiguration();
+
             if (video is Movie)
             if (video is Movie)
             {
             {
-                if (!_config.Configuration.ChapterOptions.EnableMovieChapterImageExtraction)
+                if (!options.EnableMovieChapterImageExtraction)
                 {
                 {
                     return false;
                     return false;
                 }
                 }
             }
             }
             else if (video is Episode)
             else if (video is Episode)
             {
             {
-                if (!_config.Configuration.ChapterOptions.EnableEpisodeChapterImageExtraction)
+                if (!options.EnableEpisodeChapterImageExtraction)
                 {
                 {
                     return false;
                     return false;
                 }
                 }
             }
             }
             else
             else
             {
             {
-                if (!_config.Configuration.ChapterOptions.EnableOtherVideoChapterImageExtraction)
+                if (!options.EnableOtherVideoChapterImageExtraction)
                 {
                 {
                     return false;
                     return false;
                 }
                 }

+ 26 - 1
MediaBrowser.ServerApplication/ApplicationHost.cs

@@ -41,6 +41,7 @@ using MediaBrowser.Dlna.Main;
 using MediaBrowser.MediaEncoding.BdInfo;
 using MediaBrowser.MediaEncoding.BdInfo;
 using MediaBrowser.MediaEncoding.Encoder;
 using MediaBrowser.MediaEncoding.Encoder;
 using MediaBrowser.MediaEncoding.Subtitles;
 using MediaBrowser.MediaEncoding.Subtitles;
+using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.MediaInfo;
 using MediaBrowser.Model.MediaInfo;
 using MediaBrowser.Model.System;
 using MediaBrowser.Model.System;
@@ -273,11 +274,35 @@ namespace MediaBrowser.ServerApplication
 
 
         public override Task Init(IProgress<double> progress)
         public override Task Init(IProgress<double> progress)
         {
         {
-            DeleteDeprecatedModules();
+            PerformVersionMigration();
 
 
             return base.Init(progress);
             return base.Init(progress);
         }
         }
 
 
+        private void PerformVersionMigration()
+        {
+            DeleteDeprecatedModules();
+
+            MigrateModularConfigurations();
+        }
+
+        private void MigrateModularConfigurations()
+        {
+            if (ServerConfigurationManager.Configuration.DlnaOptions != null)
+            {
+                ServerConfigurationManager.SaveConfiguration("dlna", ServerConfigurationManager.Configuration.DlnaOptions);
+                ServerConfigurationManager.Configuration.DlnaOptions = null;
+                ServerConfigurationManager.SaveConfiguration();
+            }
+
+            if (ServerConfigurationManager.Configuration.ChapterOptions != null)
+            {
+                ServerConfigurationManager.SaveConfiguration("chapters", ServerConfigurationManager.Configuration.ChapterOptions);
+                ServerConfigurationManager.Configuration.ChapterOptions = null;
+                ServerConfigurationManager.SaveConfiguration();
+            }
+        }
+
         private void DeleteDeprecatedModules()
         private void DeleteDeprecatedModules()
         {
         {
             try
             try