From 998f7c0fb8d52200b9d55c80787fb237631b5ecd Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 2 Feb 2016 21:51:00 -0500 Subject: [PATCH 01/27] update clean db task --- .../Persistence/CleanDatabaseScheduledTask.cs | 73 +++++++++++-------- 1 file changed, 43 insertions(+), 30 deletions(-) diff --git a/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs b/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs index f4eb4ef8a..1bbe42426 100644 --- a/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs +++ b/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs @@ -65,17 +65,7 @@ namespace MediaBrowser.Server.Implementations.Persistence innerProgress.RegisterAction(p => { double newPercentCommplete = .4 * p; - if (EnableUnavailableMessage) - { - var html = "Emby"; - var text = _localization.GetLocalizedString("DbUpgradeMessage"); - html += string.Format(text, newPercentCommplete.ToString("N2", CultureInfo.InvariantCulture)); - - html += ""; - html += ""; - - _httpServer.GlobalResponse = html; - } + OnProgress(newPercentCommplete); progress.Report(newPercentCommplete); }); @@ -83,12 +73,22 @@ namespace MediaBrowser.Server.Implementations.Persistence await UpdateToLatestSchema(cancellationToken, innerProgress).ConfigureAwait(false); innerProgress = new ActionableProgress(); - innerProgress.RegisterAction(p => progress.Report(40 + (.05 * p))); + innerProgress.RegisterAction(p => + { + double newPercentCommplete = 40 + (.05 * p); + OnProgress(newPercentCommplete); + progress.Report(newPercentCommplete); + }); await CleanDeadItems(cancellationToken, innerProgress).ConfigureAwait(false); progress.Report(45); innerProgress = new ActionableProgress(); - innerProgress.RegisterAction(p => progress.Report(45 + (.55 * p))); + innerProgress.RegisterAction(p => + { + double newPercentCommplete = 45 + (.55 * p); + OnProgress(newPercentCommplete); + progress.Report(newPercentCommplete); + }); await CleanDeletedItems(cancellationToken, innerProgress).ConfigureAwait(false); progress.Report(100); @@ -101,6 +101,21 @@ namespace MediaBrowser.Server.Implementations.Persistence } } + private void OnProgress(double newPercentCommplete) + { + if (EnableUnavailableMessage) + { + var html = "Emby"; + var text = _localization.GetLocalizedString("DbUpgradeMessage"); + html += string.Format(text, newPercentCommplete.ToString("N2", CultureInfo.InvariantCulture)); + + html += ""; + html += ""; + + _httpServer.GlobalResponse = html; + } + } + private async Task UpdateToLatestSchema(CancellationToken cancellationToken, IProgress progress) { var itemIds = _libraryManager.GetItemIds(new InternalItemsQuery @@ -117,27 +132,25 @@ namespace MediaBrowser.Server.Implementations.Persistence { cancellationToken.ThrowIfCancellationRequested(); - if (itemId == Guid.Empty) + if (itemId != Guid.Empty) { // Somehow some invalid data got into the db. It probably predates the boundary checking - continue; - } + var item = _libraryManager.GetItemById(itemId); - var item = _libraryManager.GetItemById(itemId); - - if (item != null) - { - try + if (item != null) { - await _itemRepo.SaveItem(item, cancellationToken).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - throw; - } - catch (Exception ex) - { - _logger.ErrorException("Error saving item", ex); + try + { + await _itemRepo.SaveItem(item, cancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception ex) + { + _logger.ErrorException("Error saving item", ex); + } } } From dbcce232bde1f8b450ede6c4615f39f4130e8a9a Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 3 Feb 2016 12:26:34 -0500 Subject: [PATCH 02/27] update naming project --- .../MediaBrowser.Server.Implementations.csproj | 4 ++-- MediaBrowser.Server.Implementations/packages.config | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 678200ca3..4d750296a 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -48,9 +48,9 @@ ..\packages\Interfaces.IO.1.0.0.5\lib\portable-net45+sl4+wp71+win8+wpa81\Interfaces.IO.dll - + False - ..\packages\MediaBrowser.Naming.1.0.0.45\lib\portable-net45+sl4+wp71+win8+wpa81\MediaBrowser.Naming.dll + ..\packages\MediaBrowser.Naming.1.0.0.46\lib\portable-net45+sl4+wp71+win8+wpa81\MediaBrowser.Naming.dll ..\packages\morelinq.1.4.0\lib\net35\MoreLinq.dll diff --git a/MediaBrowser.Server.Implementations/packages.config b/MediaBrowser.Server.Implementations/packages.config index c39d337e7..c3c6e6e25 100644 --- a/MediaBrowser.Server.Implementations/packages.config +++ b/MediaBrowser.Server.Implementations/packages.config @@ -2,7 +2,7 @@ - + From b5b8f8615edc2012e14b36853c6071678605bcc5 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 3 Feb 2016 12:26:45 -0500 Subject: [PATCH 03/27] increase startup timeout --- MediaBrowser.ServerApplication/MainStartup.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.ServerApplication/MainStartup.cs b/MediaBrowser.ServerApplication/MainStartup.cs index cf174c2d3..87acd652e 100644 --- a/MediaBrowser.ServerApplication/MainStartup.cs +++ b/MediaBrowser.ServerApplication/MainStartup.cs @@ -148,7 +148,7 @@ namespace MediaBrowser.ServerApplication { _logger.Info("Found a duplicate process. Giving it time to exit."); - if (!duplicate.WaitForExit(12000)) + if (!duplicate.WaitForExit(15000)) { _logger.Info("The duplicate process did not exit."); return true; From 89a3a77110ac864f13e8b61b7e95a58f10315298 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 3 Feb 2016 12:27:00 -0500 Subject: [PATCH 04/27] start library scan after database clean --- .../Persistence/CleanDatabaseScheduledTask.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs b/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs index 1bbe42426..26de52560 100644 --- a/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs +++ b/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs @@ -17,6 +17,7 @@ using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Localization; using MediaBrowser.Controller.Net; +using MediaBrowser.Server.Implementations.ScheduledTasks; namespace MediaBrowser.Server.Implementations.Persistence { @@ -29,11 +30,12 @@ namespace MediaBrowser.Server.Implementations.Persistence private readonly IFileSystem _fileSystem; private readonly IHttpServer _httpServer; private readonly ILocalizationManager _localization; + private readonly ITaskManager _taskManager; public const int MigrationVersion = 12; public static bool EnableUnavailableMessage = false; - public CleanDatabaseScheduledTask(ILibraryManager libraryManager, IItemRepository itemRepo, ILogger logger, IServerConfigurationManager config, IFileSystem fileSystem, IHttpServer httpServer, ILocalizationManager localization) + public CleanDatabaseScheduledTask(ILibraryManager libraryManager, IItemRepository itemRepo, ILogger logger, IServerConfigurationManager config, IFileSystem fileSystem, IHttpServer httpServer, ILocalizationManager localization, ITaskManager taskManager) { _libraryManager = libraryManager; _itemRepo = itemRepo; @@ -42,6 +44,7 @@ namespace MediaBrowser.Server.Implementations.Persistence _fileSystem = fileSystem; _httpServer = httpServer; _localization = localization; + _taskManager = taskManager; } public string Name @@ -98,6 +101,7 @@ namespace MediaBrowser.Server.Implementations.Persistence { EnableUnavailableMessage = false; _httpServer.GlobalResponse = null; + _taskManager.QueueScheduledTask(); } } From 6cb1f77789171f4be7a4c6cd9e075eb4e8b6eff0 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 3 Feb 2016 15:52:45 -0500 Subject: [PATCH 05/27] update upgrade process --- .../BaseApplicationHost.cs | 6 +-- .../ScheduledTasks/TaskManager.cs | 53 ++++++++++++++++++- .../ScheduledTasks/ITaskManager.cs | 5 ++ .../Persistence/CleanDatabaseScheduledTask.cs | 16 +++--- .../ApplicationHost.cs | 5 ++ .../Migrations/DbMigration.cs | 3 +- 6 files changed, 76 insertions(+), 12 deletions(-) diff --git a/MediaBrowser.Common.Implementations/BaseApplicationHost.cs b/MediaBrowser.Common.Implementations/BaseApplicationHost.cs index 2a93efcde..caf8f54a6 100644 --- a/MediaBrowser.Common.Implementations/BaseApplicationHost.cs +++ b/MediaBrowser.Common.Implementations/BaseApplicationHost.cs @@ -453,7 +453,7 @@ namespace MediaBrowser.Common.Implementations RegisterSingleInstance(ApplicationPaths); - TaskManager = new TaskManager(ApplicationPaths, JsonSerializer, Logger, FileSystemManager); + TaskManager = new TaskManager(ApplicationPaths, JsonSerializer, LogManager.GetLogger("TaskManager"), FileSystemManager); RegisterSingleInstance(JsonSerializer); RegisterSingleInstance(XmlSerializer); @@ -465,7 +465,7 @@ namespace MediaBrowser.Common.Implementations RegisterSingleInstance(FileSystemManager); - HttpClient = new HttpClientManager.HttpClientManager(ApplicationPaths, Logger, FileSystemManager); + HttpClient = new HttpClientManager.HttpClientManager(ApplicationPaths, LogManager.GetLogger("HttpClient"), FileSystemManager); RegisterSingleInstance(HttpClient); NetworkManager = CreateNetworkManager(LogManager.GetLogger("NetworkManager")); @@ -474,7 +474,7 @@ namespace MediaBrowser.Common.Implementations SecurityManager = new PluginSecurityManager(this, HttpClient, JsonSerializer, ApplicationPaths, LogManager); RegisterSingleInstance(SecurityManager); - InstallationManager = new InstallationManager(Logger, this, ApplicationPaths, HttpClient, JsonSerializer, SecurityManager, ConfigurationManager, FileSystemManager); + InstallationManager = new InstallationManager(LogManager.GetLogger("InstallationManager"), this, ApplicationPaths, HttpClient, JsonSerializer, SecurityManager, ConfigurationManager, FileSystemManager); RegisterSingleInstance(InstallationManager); ZipClient = new ZipClient(FileSystemManager); diff --git a/MediaBrowser.Common.Implementations/ScheduledTasks/TaskManager.cs b/MediaBrowser.Common.Implementations/ScheduledTasks/TaskManager.cs index 845c984fb..b5566650c 100644 --- a/MediaBrowser.Common.Implementations/ScheduledTasks/TaskManager.cs +++ b/MediaBrowser.Common.Implementations/ScheduledTasks/TaskManager.cs @@ -55,6 +55,25 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks private ILogger Logger { get; set; } private readonly IFileSystem _fileSystem; + private bool _suspendTriggers; + + public bool SuspendTriggers + { + get { return _suspendTriggers; } + set + { + Logger.Info("Setting SuspendTriggers to {0}", value); + var executeQueued = _suspendTriggers && !value; + + _suspendTriggers = value; + + if (executeQueued) + { + ExecuteQueuedTasks(); + } + } + } + /// /// Initializes a new instance of the class. /// @@ -151,6 +170,31 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks QueueScheduledTask(new TaskExecutionOptions()); } + public void Execute() + where T : IScheduledTask + { + var scheduledTask = ScheduledTasks.FirstOrDefault(t => t.ScheduledTask.GetType() == typeof(T)); + + if (scheduledTask == null) + { + Logger.Error("Unable to find scheduled task of type {0} in Execute.", typeof(T).Name); + } + else + { + var type = scheduledTask.ScheduledTask.GetType(); + + Logger.Info("Queueing task {0}", type.Name); + + lock (_taskQueue) + { + if (scheduledTask.State == TaskState.Idle) + { + Execute(scheduledTask, new TaskExecutionOptions()); + } + } + } + } + /// /// Queues the scheduled task. /// @@ -183,7 +227,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks lock (_taskQueue) { - if (task.State == TaskState.Idle) + if (task.State == TaskState.Idle && !SuspendTriggers) { Execute(task, options); return; @@ -273,6 +317,13 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks /// private void ExecuteQueuedTasks() { + if (SuspendTriggers) + { + return; + } + + Logger.Info("ExecuteQueuedTasks"); + // Execute queued tasks lock (_taskQueue) { diff --git a/MediaBrowser.Common/ScheduledTasks/ITaskManager.cs b/MediaBrowser.Common/ScheduledTasks/ITaskManager.cs index 8d271859e..c1dc304f6 100644 --- a/MediaBrowser.Common/ScheduledTasks/ITaskManager.cs +++ b/MediaBrowser.Common/ScheduledTasks/ITaskManager.cs @@ -66,7 +66,12 @@ namespace MediaBrowser.Common.ScheduledTasks void Cancel(IScheduledTaskWorker task); Task Execute(IScheduledTaskWorker task, TaskExecutionOptions options = null); + void Execute() + where T : IScheduledTask; + event EventHandler> TaskExecuting; event EventHandler TaskCompleted; + + bool SuspendTriggers { get; set; } } } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs b/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs index 26de52560..76a6e6d40 100644 --- a/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs +++ b/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs @@ -32,7 +32,7 @@ namespace MediaBrowser.Server.Implementations.Persistence private readonly ILocalizationManager _localization; private readonly ITaskManager _taskManager; - public const int MigrationVersion = 12; + public const int MigrationVersion = 17; public static bool EnableUnavailableMessage = false; public CleanDatabaseScheduledTask(ILibraryManager libraryManager, IItemRepository itemRepo, ILogger logger, IServerConfigurationManager config, IFileSystem fileSystem, IHttpServer httpServer, ILocalizationManager localization, ITaskManager taskManager) @@ -97,12 +97,20 @@ namespace MediaBrowser.Server.Implementations.Persistence await _itemRepo.UpdateInheritedValues(cancellationToken).ConfigureAwait(false); + if (_config.Configuration.MigrationVersion < MigrationVersion) + { + _config.Configuration.MigrationVersion = MigrationVersion; + _config.SaveConfiguration(); + } + if (EnableUnavailableMessage) { EnableUnavailableMessage = false; _httpServer.GlobalResponse = null; _taskManager.QueueScheduledTask(); } + + _taskManager.SuspendTriggers = false; } private void OnProgress(double newPercentCommplete) @@ -164,12 +172,6 @@ namespace MediaBrowser.Server.Implementations.Persistence progress.Report(percent * 100); } - if (_config.Configuration.MigrationVersion < MigrationVersion) - { - _config.Configuration.MigrationVersion = MigrationVersion; - _config.SaveConfiguration(); - } - progress.Report(100); } diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs index f75aeebdd..7f79653af 100644 --- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs +++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs @@ -317,6 +317,11 @@ namespace MediaBrowser.Server.Startup.Common /// Task. public override async Task RunStartupTasks() { + if (ServerConfigurationManager.Configuration.MigrationVersion < CleanDatabaseScheduledTask.MigrationVersion) + { + TaskManager.SuspendTriggers = true; + } + await base.RunStartupTasks().ConfigureAwait(false); Logger.Info("ServerId: {0}", SystemId); diff --git a/MediaBrowser.Server.Startup.Common/Migrations/DbMigration.cs b/MediaBrowser.Server.Startup.Common/Migrations/DbMigration.cs index 1df49845b..959543893 100644 --- a/MediaBrowser.Server.Startup.Common/Migrations/DbMigration.cs +++ b/MediaBrowser.Server.Startup.Common/Migrations/DbMigration.cs @@ -20,13 +20,14 @@ namespace MediaBrowser.Server.Startup.Common.Migrations { if (_config.Configuration.MigrationVersion < CleanDatabaseScheduledTask.MigrationVersion) { + _taskManager.SuspendTriggers = true; CleanDatabaseScheduledTask.EnableUnavailableMessage = true; Task.Run(async () => { await Task.Delay(1000).ConfigureAwait(false); - _taskManager.QueueScheduledTask(); + _taskManager.Execute(); }); } } From 00ae190a276f51f488752ab76cee131b16671fa2 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 3 Feb 2016 16:56:00 -0500 Subject: [PATCH 06/27] update upgrade process --- .../HttpServer/HttpListenerHost.cs | 14 +++++++++++--- .../Persistence/CleanDatabaseScheduledTask.cs | 14 ++++++++++++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs index 78d7a480e..d91d80dbd 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -266,11 +266,19 @@ namespace MediaBrowser.Server.Implementations.HttpServer {".html", 0} }; - private bool EnableLogging(string url) + private bool EnableLogging(string url, string localPath) { var extension = GetExtension(url); - return string.IsNullOrWhiteSpace(extension) || !_skipLogExtensions.ContainsKey(extension); + if (string.IsNullOrWhiteSpace(extension) || !_skipLogExtensions.ContainsKey(extension)) + { + if (string.IsNullOrWhiteSpace(localPath) || localPath.IndexOf("system/ping", StringComparison.OrdinalIgnoreCase) == -1) + { + return true; + } + } + + return false; } private string GetExtension(string url) @@ -296,7 +304,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer var localPath = url.LocalPath; var urlString = url.OriginalString; - var enableLog = EnableLogging(urlString); + var enableLog = EnableLogging(urlString, localPath); if (enableLog) { diff --git a/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs b/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs index 76a6e6d40..f7c6fe502 100644 --- a/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs +++ b/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs @@ -64,6 +64,8 @@ namespace MediaBrowser.Server.Implementations.Persistence public async Task Execute(CancellationToken cancellationToken, IProgress progress) { + OnProgress(0); + var innerProgress = new ActionableProgress(); innerProgress.RegisterAction(p => { @@ -146,6 +148,8 @@ namespace MediaBrowser.Server.Implementations.Persistence if (itemId != Guid.Empty) { + LogMessage(string.Format("Querying item {0}", itemId)); + // Somehow some invalid data got into the db. It probably predates the boundary checking var item = _libraryManager.GetItemById(itemId); @@ -153,6 +157,8 @@ namespace MediaBrowser.Server.Implementations.Persistence { try { + LogMessage(string.Format("Saving item {0}", itemId)); + await _itemRepo.SaveItem(item, cancellationToken).ConfigureAwait(false); } catch (OperationCanceledException) @@ -175,6 +181,14 @@ namespace MediaBrowser.Server.Implementations.Persistence progress.Report(100); } + private void LogMessage(string msg) + { + if (EnableUnavailableMessage) + { + _logger.Info(msg); + } + } + private async Task CleanDeadItems(CancellationToken cancellationToken, IProgress progress) { var itemIds = _libraryManager.GetItemIds(new InternalItemsQuery From 0b5b409354909a0f446aea9f69ae4fd7eb6be35d Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 3 Feb 2016 17:42:33 -0500 Subject: [PATCH 07/27] update migration --- .../Persistence/CleanDatabaseScheduledTask.cs | 11 ++++++----- .../Persistence/SqliteItemRepository.cs | 6 ++++++ MediaBrowser.Server.Startup.Common/ApplicationHost.cs | 4 ++-- .../Migrations/DbMigration.cs | 2 +- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs b/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs index f7c6fe502..5c600c249 100644 --- a/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs +++ b/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs @@ -10,6 +10,7 @@ using MediaBrowser.Model.Logging; using System; using System.Collections.Generic; using System.Globalization; +using System.Linq; using System.Threading; using System.Threading.Tasks; using CommonIO; @@ -32,7 +33,7 @@ namespace MediaBrowser.Server.Implementations.Persistence private readonly ILocalizationManager _localization; private readonly ITaskManager _taskManager; - public const int MigrationVersion = 17; + public const int MigrationVersion = 20; public static bool EnableUnavailableMessage = false; public CleanDatabaseScheduledTask(ILibraryManager libraryManager, IItemRepository itemRepo, ILogger logger, IServerConfigurationManager config, IFileSystem fileSystem, IHttpServer httpServer, ILocalizationManager localization, ITaskManager taskManager) @@ -66,6 +67,10 @@ namespace MediaBrowser.Server.Implementations.Persistence { OnProgress(0); + // Ensure these objects are out of the database. + var rootChildren = _libraryManager.RootFolder.Children.ToList(); + rootChildren = _libraryManager.GetUserRootFolder().Children.ToList(); + var innerProgress = new ActionableProgress(); innerProgress.RegisterAction(p => { @@ -148,8 +153,6 @@ namespace MediaBrowser.Server.Implementations.Persistence if (itemId != Guid.Empty) { - LogMessage(string.Format("Querying item {0}", itemId)); - // Somehow some invalid data got into the db. It probably predates the boundary checking var item = _libraryManager.GetItemById(itemId); @@ -157,8 +160,6 @@ namespace MediaBrowser.Server.Implementations.Persistence { try { - LogMessage(string.Format("Saving item {0}", itemId)); - await _itemRepo.SaveItem(item, cancellationToken).ConfigureAwait(false); } catch (OperationCanceledException) diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs index 7898dc1ef..3dc37110e 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs +++ b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs @@ -583,16 +583,22 @@ namespace MediaBrowser.Server.Implementations.Persistence CheckDisposed(); + _logger.Info("SaveItems waiting on write lock"); + await _writeLock.WaitAsync(cancellationToken).ConfigureAwait(false); IDbTransaction transaction = null; try { + _logger.Info("SaveItems creating transaction"); + transaction = _connection.BeginTransaction(); foreach (var item in items) { + _logger.Info("Saving {0}", item.Id); + cancellationToken.ThrowIfCancellationRequested(); var index = 0; diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs index 7f79653af..46dfd4469 100644 --- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs +++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs @@ -328,6 +328,8 @@ namespace MediaBrowser.Server.Startup.Common Logger.Info("Core startup complete"); HttpServer.GlobalResponse = null; + PerformPostInitMigrations(); + Parallel.ForEach(GetExports(), entryPoint => { try @@ -341,8 +343,6 @@ namespace MediaBrowser.Server.Startup.Common }); LogManager.RemoveConsoleOutput(); - - PerformPostInitMigrations(); } public override Task Init(IProgress progress) diff --git a/MediaBrowser.Server.Startup.Common/Migrations/DbMigration.cs b/MediaBrowser.Server.Startup.Common/Migrations/DbMigration.cs index 959543893..5a70467f7 100644 --- a/MediaBrowser.Server.Startup.Common/Migrations/DbMigration.cs +++ b/MediaBrowser.Server.Startup.Common/Migrations/DbMigration.cs @@ -25,7 +25,7 @@ namespace MediaBrowser.Server.Startup.Common.Migrations Task.Run(async () => { - await Task.Delay(1000).ConfigureAwait(false); + await Task.Delay(100).ConfigureAwait(false); _taskManager.Execute(); }); From d8c654f3292e14ba050c3b0f116e41eea6797e57 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 4 Feb 2016 12:50:24 -0500 Subject: [PATCH 08/27] stub out pin login service --- MediaBrowser.Api/MediaBrowser.Api.csproj | 1 + MediaBrowser.Api/PinLoginService.cs | 146 +++++++++++++++++++++++ 2 files changed, 147 insertions(+) create mode 100644 MediaBrowser.Api/PinLoginService.cs diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index 7e55446ae..f711c69e6 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -80,6 +80,7 @@ + diff --git a/MediaBrowser.Api/PinLoginService.cs b/MediaBrowser.Api/PinLoginService.cs new file mode 100644 index 000000000..81d590395 --- /dev/null +++ b/MediaBrowser.Api/PinLoginService.cs @@ -0,0 +1,146 @@ +using System; +using System.Collections.Concurrent; +using System.Globalization; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Net; +using MediaBrowser.Model.Connect; +using ServiceStack; + +namespace MediaBrowser.Api +{ + [Route("/Auth/Pin", "POST", Summary = "Creates a pin request")] + public class CreatePinRequest : IReturn + { + public string DeviceId { get; set; } + } + + [Route("/Auth/Pin", "GET", Summary = "Gets pin status")] + public class GetPinStatusRequest : IReturn + { + public string DeviceId { get; set; } + public string Pin { get; set; } + } + + [Route("/Auth/Pin/Exchange", "POST", Summary = "Exchanges a pin")] + public class ExchangePinRequest : IReturn + { + public string DeviceId { get; set; } + public string Pin { get; set; } + } + + [Route("/Auth/Pin/Validate", "POST", Summary = "Validates a pin")] + [Authenticated] + public class ValidatePinRequest : IReturnVoid + { + public string Pin { get; set; } + } + + public class PinLoginService : BaseApiService + { + private readonly ConcurrentDictionary _activeRequests = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + + public object Post(CreatePinRequest request) + { + var pin = GetNewPin(5); + var key = GetKey(request.DeviceId, pin); + + var value = new MyPinStatus + { + CreationTimeUtc = DateTime.UtcNow, + IsConfirmed = false, + IsExpired = false, + Pin = pin + }; + + _activeRequests.AddOrUpdate(key, value, (k, v) => value); + + return ToOptimizedResult(new PinCreationResult + { + DeviceId = request.DeviceId, + IsConfirmed = false, + IsExpired = false, + Pin = pin + }); + } + + public object Get(GetPinStatusRequest request) + { + MyPinStatus status; + + if (!_activeRequests.TryGetValue(GetKey(request.DeviceId, request.Pin), out status)) + { + throw new ResourceNotFoundException(); + } + + CheckExpired(status); + + if (status.IsExpired) + { + throw new ResourceNotFoundException(); + } + + return ToOptimizedResult(new PinStatusResult + { + Pin = status.Pin, + IsConfirmed = status.IsConfirmed, + IsExpired = status.IsExpired + }); + } + + public object Post(ExchangePinRequest request) + { + MyPinStatus status; + + if (!_activeRequests.TryGetValue(GetKey(request.DeviceId, request.Pin), out status)) + { + throw new ResourceNotFoundException(); + } + + CheckExpired(status); + + if (status.IsExpired) + { + throw new ResourceNotFoundException(); + } + + return ToOptimizedResult(new PinExchangeResult + { + }); + } + + public void Post(ValidatePinRequest request) + { + } + + private void CheckExpired(MyPinStatus status) + { + if ((DateTime.UtcNow - status.CreationTimeUtc).TotalMinutes > 10) + { + status.IsExpired = true; + } + } + + private string GetNewPin(int length) + { + var pin = string.Empty; + + while (pin.Length < length) + { + var digit = new Random().Next(0, 9); + pin += digit.ToString(CultureInfo.InvariantCulture); + } + + return pin; + } + + private string GetKey(string deviceId, string pin) + { + return deviceId + pin; + } + + public class MyPinStatus : PinStatusResult + { + public DateTime CreationTimeUtc { get; set; } + } + } +} From 7614707dad5aff2847d46105406828f01b3bbd85 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 4 Feb 2016 13:03:40 -0500 Subject: [PATCH 09/27] update pin login service --- MediaBrowser.Api/PinLoginService.cs | 92 +++++++++++++++++++++++------ 1 file changed, 74 insertions(+), 18 deletions(-) diff --git a/MediaBrowser.Api/PinLoginService.cs b/MediaBrowser.Api/PinLoginService.cs index 81d590395..8b63de10a 100644 --- a/MediaBrowser.Api/PinLoginService.cs +++ b/MediaBrowser.Api/PinLoginService.cs @@ -11,20 +11,25 @@ namespace MediaBrowser.Api [Route("/Auth/Pin", "POST", Summary = "Creates a pin request")] public class CreatePinRequest : IReturn { + [ApiMember(Name = "DeviceId", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] public string DeviceId { get; set; } } [Route("/Auth/Pin", "GET", Summary = "Gets pin status")] public class GetPinStatusRequest : IReturn { + [ApiMember(Name = "DeviceId", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] public string DeviceId { get; set; } + [ApiMember(Name = "Pin", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] public string Pin { get; set; } } [Route("/Auth/Pin/Exchange", "POST", Summary = "Exchanges a pin")] public class ExchangePinRequest : IReturn { + [ApiMember(Name = "DeviceId", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] public string DeviceId { get; set; } + [ApiMember(Name = "Pin", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] public string Pin { get; set; } } @@ -32,6 +37,7 @@ namespace MediaBrowser.Api [Authenticated] public class ValidatePinRequest : IReturnVoid { + [ApiMember(Name = "Pin", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] public string Pin { get; set; } } @@ -41,18 +47,18 @@ namespace MediaBrowser.Api public object Post(CreatePinRequest request) { - var pin = GetNewPin(5); - var key = GetKey(request.DeviceId, pin); + var pin = GetNewPin(); var value = new MyPinStatus { CreationTimeUtc = DateTime.UtcNow, IsConfirmed = false, IsExpired = false, - Pin = pin + Pin = pin, + DeviceId = request.DeviceId }; - _activeRequests.AddOrUpdate(key, value, (k, v) => value); + _activeRequests.AddOrUpdate(pin, value, (k, v) => value); return ToOptimizedResult(new PinCreationResult { @@ -67,17 +73,12 @@ namespace MediaBrowser.Api { MyPinStatus status; - if (!_activeRequests.TryGetValue(GetKey(request.DeviceId, request.Pin), out status)) + if (!_activeRequests.TryGetValue(request.Pin, out status)) { throw new ResourceNotFoundException(); } - CheckExpired(status); - - if (status.IsExpired) - { - throw new ResourceNotFoundException(); - } + EnsureValid(request.DeviceId, status); return ToOptimizedResult(new PinStatusResult { @@ -91,37 +92,78 @@ namespace MediaBrowser.Api { MyPinStatus status; - if (!_activeRequests.TryGetValue(GetKey(request.DeviceId, request.Pin), out status)) + if (!_activeRequests.TryGetValue(request.Pin, out status)) { throw new ResourceNotFoundException(); } - CheckExpired(status); + EnsureValid(request.DeviceId, status); - if (status.IsExpired) + if (!status.IsConfirmed) { throw new ResourceNotFoundException(); } return ToOptimizedResult(new PinExchangeResult { + // TODO: Add access token + UserId = status.UserId }); } public void Post(ValidatePinRequest request) { + MyPinStatus status; + + if (!_activeRequests.TryGetValue(request.Pin, out status)) + { + throw new ResourceNotFoundException(); + } + + EnsureValid(status); + + status.IsConfirmed = true; + status.UserId = AuthorizationContext.GetAuthorizationInfo(Request).UserId; } - private void CheckExpired(MyPinStatus status) + private void EnsureValid(string requestedDeviceId, MyPinStatus status) + { + if (!string.Equals(requestedDeviceId, status.DeviceId, StringComparison.OrdinalIgnoreCase)) + { + throw new ResourceNotFoundException(); + } + + EnsureValid(status); + } + + private void EnsureValid(MyPinStatus status) { if ((DateTime.UtcNow - status.CreationTimeUtc).TotalMinutes > 10) { status.IsExpired = true; } + + if (status.IsExpired) + { + throw new ResourceNotFoundException(); + } } - private string GetNewPin(int length) + private string GetNewPin() { + var pin = GetNewPinInternal(); + + while (IsPinActive(pin)) + { + pin = GetNewPinInternal(); + } + + return pin; + } + + private string GetNewPinInternal() + { + var length = 5; var pin = string.Empty; while (pin.Length < length) @@ -133,14 +175,28 @@ namespace MediaBrowser.Api return pin; } - private string GetKey(string deviceId, string pin) + private bool IsPinActive(string pin) { - return deviceId + pin; + MyPinStatus status; + + if (!_activeRequests.TryGetValue(pin, out status)) + { + return true; + } + + if (status.IsExpired) + { + return true; + } + + return false; } public class MyPinStatus : PinStatusResult { public DateTime CreationTimeUtc { get; set; } + public string DeviceId { get; set; } + public string UserId { get; set; } } } } From d28ef71d93ea7fe50343f82f575637307b4d74bf Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 4 Feb 2016 13:04:04 -0500 Subject: [PATCH 10/27] update db migration --- .../HttpServer/HttpListenerHost.cs | 12 +++++++----- .../Persistence/CleanDatabaseScheduledTask.cs | 11 ++--------- .../Persistence/SqliteItemRepository.cs | 6 ------ .../ApplicationHost.cs | 3 ++- .../Migrations/DbMigration.cs | 3 ++- 5 files changed, 13 insertions(+), 22 deletions(-) diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs index d91d80dbd..a11eb3aaa 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -241,7 +241,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer } catch (Exception errorEx) { - _logger.ErrorException("Error this.ProcessRequest(context)(Exception while writing error to the response)", errorEx); + //_logger.ErrorException("Error this.ProcessRequest(context)(Exception while writing error to the response)", errorEx); } } @@ -350,10 +350,12 @@ namespace MediaBrowser.Server.Implementations.HttpServer if (!string.IsNullOrWhiteSpace(GlobalResponse)) { - httpRes.Write(GlobalResponse); - httpRes.ContentType = "text/plain"; - - if (!string.Equals(GetExtension(urlString), "html", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(GetExtension(urlString), "html", StringComparison.OrdinalIgnoreCase)) + { + httpRes.Write(GlobalResponse); + httpRes.ContentType = "text/plain"; + } + else { httpRes.StatusCode = 503; } diff --git a/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs b/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs index 5c600c249..0959e2123 100644 --- a/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs +++ b/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs @@ -67,7 +67,8 @@ namespace MediaBrowser.Server.Implementations.Persistence { OnProgress(0); - // Ensure these objects are out of the database. + // Ensure these objects are lazy loaded. + // Without this there is a deadlock that will need to be investigated var rootChildren = _libraryManager.RootFolder.Children.ToList(); rootChildren = _libraryManager.GetUserRootFolder().Children.ToList(); @@ -182,14 +183,6 @@ namespace MediaBrowser.Server.Implementations.Persistence progress.Report(100); } - private void LogMessage(string msg) - { - if (EnableUnavailableMessage) - { - _logger.Info(msg); - } - } - private async Task CleanDeadItems(CancellationToken cancellationToken, IProgress progress) { var itemIds = _libraryManager.GetItemIds(new InternalItemsQuery diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs index 3dc37110e..7898dc1ef 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs +++ b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs @@ -583,22 +583,16 @@ namespace MediaBrowser.Server.Implementations.Persistence CheckDisposed(); - _logger.Info("SaveItems waiting on write lock"); - await _writeLock.WaitAsync(cancellationToken).ConfigureAwait(false); IDbTransaction transaction = null; try { - _logger.Info("SaveItems creating transaction"); - transaction = _connection.BeginTransaction(); foreach (var item in items) { - _logger.Info("Saving {0}", item.Id); - cancellationToken.ThrowIfCancellationRequested(); var index = 0; diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs index 46dfd4469..36d15b95a 100644 --- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs +++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs @@ -317,7 +317,8 @@ namespace MediaBrowser.Server.Startup.Common /// Task. public override async Task RunStartupTasks() { - if (ServerConfigurationManager.Configuration.MigrationVersion < CleanDatabaseScheduledTask.MigrationVersion) + if (ServerConfigurationManager.Configuration.MigrationVersion < CleanDatabaseScheduledTask.MigrationVersion && + ServerConfigurationManager.Configuration.IsStartupWizardCompleted) { TaskManager.SuspendTriggers = true; } diff --git a/MediaBrowser.Server.Startup.Common/Migrations/DbMigration.cs b/MediaBrowser.Server.Startup.Common/Migrations/DbMigration.cs index 5a70467f7..3cd92bcde 100644 --- a/MediaBrowser.Server.Startup.Common/Migrations/DbMigration.cs +++ b/MediaBrowser.Server.Startup.Common/Migrations/DbMigration.cs @@ -18,7 +18,8 @@ namespace MediaBrowser.Server.Startup.Common.Migrations public void Run() { - if (_config.Configuration.MigrationVersion < CleanDatabaseScheduledTask.MigrationVersion) + if (_config.Configuration.MigrationVersion < CleanDatabaseScheduledTask.MigrationVersion && + _config.Configuration.IsStartupWizardCompleted) { _taskManager.SuspendTriggers = true; CleanDatabaseScheduledTask.EnableUnavailableMessage = true; From d1de9e01795d5984a52845817a63ec64e486218b Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 4 Feb 2016 14:31:04 -0500 Subject: [PATCH 11/27] update sync encoding to match streaming --- .../Encoder/AudioEncoder.cs | 24 +- .../Encoder/BaseEncoder.cs | 215 ++++++++++++------ .../Encoder/EncodingJobFactory.cs | 183 ++++++++------- .../Encoder/VideoEncoder.cs | 47 ++-- 4 files changed, 287 insertions(+), 182 deletions(-) diff --git a/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs index b488741d1..a4d4797eb 100644 --- a/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs @@ -19,38 +19,40 @@ namespace MediaBrowser.MediaEncoding.Encoder { } - protected override string GetCommandLineArguments(EncodingJob job) + protected override string GetCommandLineArguments(EncodingJob state) { var audioTranscodeParams = new List(); - var bitrate = job.OutputAudioBitrate; + var bitrate = state.OutputAudioBitrate; if (bitrate.HasValue) { audioTranscodeParams.Add("-ab " + bitrate.Value.ToString(UsCulture)); } - if (job.OutputAudioChannels.HasValue) + if (state.OutputAudioChannels.HasValue) { - audioTranscodeParams.Add("-ac " + job.OutputAudioChannels.Value.ToString(UsCulture)); + audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(UsCulture)); } - if (job.OutputAudioSampleRate.HasValue) + if (state.OutputAudioSampleRate.HasValue) { - audioTranscodeParams.Add("-ar " + job.OutputAudioSampleRate.Value.ToString(UsCulture)); + audioTranscodeParams.Add("-ar " + state.OutputAudioSampleRate.Value.ToString(UsCulture)); } - var threads = GetNumberOfThreads(job, false); + const string vn = " -vn"; - var inputModifier = GetInputModifier(job); + var threads = GetNumberOfThreads(state, false); + + var inputModifier = GetInputModifier(state); return string.Format("{0} {1} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1 -y \"{5}\"", inputModifier, - GetInputArgument(job), + GetInputArgument(state), threads, - " -vn", + vn, string.Join(" ", audioTranscodeParams.ToArray()), - job.OutputFilePath).Trim(); + state.OutputFilePath).Trim(); } protected override string GetOutputFileExtension(EncodingJob state) diff --git a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs index 98e4a58a6..3a4a12b35 100644 --- a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs @@ -303,15 +303,15 @@ namespace MediaBrowser.MediaEncoding.Encoder return job.Options.CpuCoreLimit ?? 0; } - protected string GetInputModifier(EncodingJob job, bool genPts = true) + protected string GetInputModifier(EncodingJob state, bool genPts = true) { var inputModifier = string.Empty; - var probeSize = GetProbeSizeArgument(job); + var probeSize = GetProbeSizeArgument(state); inputModifier += " " + probeSize; inputModifier = inputModifier.Trim(); - var userAgentParam = GetUserAgentParam(job); + var userAgentParam = GetUserAgentParam(state); if (!string.IsNullOrWhiteSpace(userAgentParam)) { @@ -320,35 +320,43 @@ namespace MediaBrowser.MediaEncoding.Encoder inputModifier = inputModifier.Trim(); - inputModifier += " " + GetFastSeekCommandLineParameter(job.Options); + inputModifier += " " + GetFastSeekCommandLineParameter(state.Options); inputModifier = inputModifier.Trim(); - if (job.IsVideoRequest && genPts) + if (state.IsVideoRequest && genPts) { inputModifier += " -fflags +genpts"; } - if (!string.IsNullOrEmpty(job.InputAudioSync)) + if (!string.IsNullOrEmpty(state.InputAudioSync)) { - inputModifier += " -async " + job.InputAudioSync; + inputModifier += " -async " + state.InputAudioSync; } - if (!string.IsNullOrEmpty(job.InputVideoSync)) + if (!string.IsNullOrEmpty(state.InputVideoSync)) { - inputModifier += " -vsync " + job.InputVideoSync; + inputModifier += " -vsync " + state.InputVideoSync; } - if (job.ReadInputAtNativeFramerate) + if (state.ReadInputAtNativeFramerate) { inputModifier += " -re"; } - var videoDecoder = GetVideoDecoder(job); + var videoDecoder = GetVideoDecoder(state); if (!string.IsNullOrWhiteSpace(videoDecoder)) { inputModifier += " " + videoDecoder; } + //if (state.IsVideoRequest) + //{ + // if (string.Equals(state.OutputContainer, "mkv", StringComparison.OrdinalIgnoreCase)) + // { + // //inputModifier += " -noaccurate_seek"; + // } + //} + return inputModifier; } @@ -392,11 +400,11 @@ namespace MediaBrowser.MediaEncoding.Encoder return null; } - private string GetUserAgentParam(EncodingJob job) + private string GetUserAgentParam(EncodingJob state) { string useragent = null; - job.RemoteHttpHeaders.TryGetValue("User-Agent", out useragent); + state.RemoteHttpHeaders.TryGetValue("User-Agent", out useragent); if (!string.IsNullOrWhiteSpace(useragent)) { @@ -409,31 +417,31 @@ namespace MediaBrowser.MediaEncoding.Encoder /// /// Gets the probe size argument. /// - /// The job. + /// The state. /// System.String. - private string GetProbeSizeArgument(EncodingJob job) + private string GetProbeSizeArgument(EncodingJob state) { - if (job.PlayableStreamFileNames.Count > 0) + if (state.PlayableStreamFileNames.Count > 0) { - return MediaEncoder.GetProbeSizeArgument(job.PlayableStreamFileNames.ToArray(), job.InputProtocol); + return MediaEncoder.GetProbeSizeArgument(state.PlayableStreamFileNames.ToArray(), state.InputProtocol); } - return MediaEncoder.GetProbeSizeArgument(new[] { job.MediaPath }, job.InputProtocol); + return MediaEncoder.GetProbeSizeArgument(new[] { state.MediaPath }, state.InputProtocol); } /// /// Gets the fast seek command line parameter. /// - /// The options. + /// The request. /// System.String. /// The fast seek command line parameter. - protected string GetFastSeekCommandLineParameter(EncodingJobOptions options) + protected string GetFastSeekCommandLineParameter(EncodingJobOptions request) { - var time = options.StartTimeTicks; + var time = request.StartTimeTicks ?? 0; - if (time.HasValue && time.Value > 0) + if (time > 0) { - return string.Format("-ss {0}", MediaEncoder.GetTimeParameter(time.Value)); + return string.Format("-ss {0}", MediaEncoder.GetTimeParameter(time)); } return string.Empty; @@ -442,34 +450,35 @@ namespace MediaBrowser.MediaEncoding.Encoder /// /// Gets the input argument. /// - /// The job. + /// The state. /// System.String. - protected string GetInputArgument(EncodingJob job) + protected string GetInputArgument(EncodingJob state) { - var arg = "-i " + GetInputPathArgument(job); + var arg = string.Format("-i {0}", GetInputPathArgument(state)); - if (job.SubtitleStream != null) + if (state.SubtitleStream != null) { - if (job.SubtitleStream.IsExternal && !job.SubtitleStream.IsTextSubtitleStream) + if (state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream) { - arg += " -i \"" + job.SubtitleStream.Path + "\""; + arg += " -i \"" + state.SubtitleStream.Path + "\""; } } - return arg; + return arg.Trim(); } - private string GetInputPathArgument(EncodingJob job) + private string GetInputPathArgument(EncodingJob state) { - var protocol = job.InputProtocol; + var protocol = state.InputProtocol; + var mediaPath = state.MediaPath ?? string.Empty; - var inputPath = new[] { job.MediaPath }; + var inputPath = new[] { mediaPath }; - if (job.IsInputVideo) + if (state.IsInputVideo) { - if (!(job.VideoType == VideoType.Iso && job.IsoMount == null)) + if (!(state.VideoType == VideoType.Iso && state.IsoMount == null)) { - inputPath = MediaEncoderHelpers.GetInputArgument(FileSystem, job.MediaPath, job.InputProtocol, job.IsoMount, job.PlayableStreamFileNames); + inputPath = MediaEncoderHelpers.GetInputArgument(FileSystem, mediaPath, state.InputProtocol, state.IsoMount, state.PlayableStreamFileNames); } } @@ -491,7 +500,7 @@ namespace MediaBrowser.MediaEncoding.Encoder }, false, cancellationToken).ConfigureAwait(false); - AttachMediaStreamInfo(state, liveStreamResponse.MediaSource, state.Options); + AttachMediaSourceInfo(state, liveStreamResponse.MediaSource, state.Options); if (state.IsVideoRequest) { @@ -505,11 +514,11 @@ namespace MediaBrowser.MediaEncoding.Encoder } } - private void AttachMediaStreamInfo(EncodingJob state, + private void AttachMediaSourceInfo(EncodingJob state, MediaSourceInfo mediaSource, EncodingJobOptions videoRequest) { - EncodingJobFactory.AttachMediaStreamInfo(state, mediaSource, videoRequest); + EncodingJobFactory.AttachMediaSourceInfo(state, mediaSource, videoRequest); } /// @@ -572,7 +581,7 @@ namespace MediaBrowser.MediaEncoding.Encoder { param = "-preset superfast"; - param += " -crf 28"; + param += " -crf 23"; } else if (string.Equals(videoCodec, "libx265", StringComparison.OrdinalIgnoreCase)) @@ -582,6 +591,19 @@ namespace MediaBrowser.MediaEncoding.Encoder param += " -crf 28"; } + // h264 (h264_qsv) + else if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)) + { + param = "-preset 7 -look_ahead 0"; + + } + + // h264 (libnvenc) + else if (string.Equals(videoCodec, "libnvenc", StringComparison.OrdinalIgnoreCase)) + { + param = "-preset high-performance"; + } + // webm else if (string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase)) { @@ -644,9 +666,53 @@ namespace MediaBrowser.MediaEncoding.Encoder param += " -profile:v " + state.Options.Profile; } - if (state.Options.Level.HasValue) + var levelString = state.Options.Level.HasValue ? state.Options.Level.Value.ToString(CultureInfo.InvariantCulture) : null; + + if (!string.IsNullOrEmpty(levelString)) { - param += " -level " + state.Options.Level.Value.ToString(UsCulture); + var h264Encoder = EncodingJobFactory.GetH264Encoder(state, GetEncodingOptions()); + + // h264_qsv and libnvenc expect levels to be expressed as a decimal. libx264 supports decimal and non-decimal format + if (String.Equals(h264Encoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) || String.Equals(h264Encoder, "libnvenc", StringComparison.OrdinalIgnoreCase)) + { + switch (levelString) + { + case "30": + param += " -level 3"; + break; + case "31": + param += " -level 3.1"; + break; + case "32": + param += " -level 3.2"; + break; + case "40": + param += " -level 4"; + break; + case "41": + param += " -level 4.1"; + break; + case "42": + param += " -level 4.2"; + break; + case "50": + param += " -level 5"; + break; + case "51": + param += " -level 5.1"; + break; + case "52": + param += " -level 5.2"; + break; + default: + param += " -level " + levelString; + break; + } + } + else + { + param += " -level " + levelString; + } } return "-pix_fmt yuv420p " + param; @@ -658,15 +724,8 @@ namespace MediaBrowser.MediaEncoding.Encoder if (bitrate.HasValue) { - var hasFixedResolution = state.Options.HasFixedResolution; - if (string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase)) { - if (hasFixedResolution) - { - return string.Format(" -minrate:v ({0}*.90) -maxrate:v ({0}*1.10) -bufsize:v {0} -b:v {0}", bitrate.Value.ToString(UsCulture)); - } - // With vpx when crf is used, b:v becomes a max rate // https://trac.ffmpeg.org/wiki/vpxEncodingGuide. But higher bitrate source files -b:v causes judder so limite the bitrate but dont allow it to "saturate" the bitrate. So dont contrain it down just up. return string.Format(" -maxrate:v {0} -bufsize:v ({0}*2) -b:v {0}", bitrate.Value.ToString(UsCulture)); @@ -677,20 +736,15 @@ namespace MediaBrowser.MediaEncoding.Encoder return string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture)); } - // H264 - if (hasFixedResolution) + // h264 + if (isHls) { - if (isHls) - { - return string.Format(" -b:v {0} -maxrate ({0}*.80) -bufsize {0}", bitrate.Value.ToString(UsCulture)); - } - - return string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture)); + return string.Format(" -b:v {0} -maxrate {0} -bufsize {1}", + bitrate.Value.ToString(UsCulture), + (bitrate.Value * 2).ToString(UsCulture)); } - return string.Format(" -maxrate {0} -bufsize {1}", - bitrate.Value.ToString(UsCulture), - (bitrate.Value * 2).ToString(UsCulture)); + return string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture)); } return string.Empty; @@ -698,20 +752,23 @@ namespace MediaBrowser.MediaEncoding.Encoder protected double? GetFramerateParam(EncodingJob state) { - if (state.Options.Framerate.HasValue) + if (state.Options != null) { - return state.Options.Framerate.Value; - } - - var maxrate = state.Options.MaxFramerate; - - if (maxrate.HasValue && state.VideoStream != null) - { - var contentRate = state.VideoStream.AverageFrameRate ?? state.VideoStream.RealFrameRate; - - if (contentRate.HasValue && contentRate.Value > maxrate.Value) + if (state.Options.Framerate.HasValue) { - return maxrate; + return state.Options.Framerate.Value; + } + + var maxrate = state.Options.MaxFramerate; + + if (maxrate.HasValue && state.VideoStream != null) + { + var contentRate = state.VideoStream.AverageFrameRate ?? state.VideoStream.RealFrameRate; + + if (contentRate.HasValue && contentRate.Value > maxrate.Value) + { + return maxrate; + } } } @@ -852,7 +909,7 @@ namespace MediaBrowser.MediaEncoding.Encoder { var maxWidthParam = request.MaxWidth.Value.ToString(UsCulture); - filters.Add(string.Format("scale=min(iw\\,{0}):trunc(ow/dar/2)*2", maxWidthParam)); + filters.Add(string.Format("scale=trunc(min(max(iw\\,ih*dar)\\,{0})/2)*2:trunc(ow/dar/2)*2", maxWidthParam)); } // If a max height was requested @@ -863,6 +920,14 @@ namespace MediaBrowser.MediaEncoding.Encoder filters.Add(string.Format("scale=trunc(oh*a/2)*2:min(ih\\,{0})", maxHeightParam)); } + if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)) + { + if (filters.Count > 1) + { + //filters[filters.Count - 1] += ":flags=fast_bilinear"; + } + } + var output = string.Empty; if (state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream) @@ -917,8 +982,10 @@ namespace MediaBrowser.MediaEncoding.Encoder seconds.ToString(UsCulture)); } + var mediaPath = state.MediaPath ?? string.Empty; + return string.Format("subtitles='{0}:si={1}',setpts=PTS -{2}/TB", - MediaEncoder.EscapeSubtitleFilterPath(state.MediaPath), + MediaEncoder.EscapeSubtitleFilterPath(mediaPath), state.InternalSubtitleStreamOffset.ToString(UsCulture), seconds.ToString(UsCulture)); } diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs index 9cdc4a7bf..27072efe1 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs @@ -10,6 +10,7 @@ using MediaBrowser.Model.MediaInfo; using System; using System.Collections.Generic; using System.Globalization; +using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -66,38 +67,56 @@ namespace MediaBrowser.MediaEncoding.Encoder ? mediaSources.First() : mediaSources.First(i => string.Equals(i.Id, request.MediaSourceId)); - AttachMediaStreamInfo(state, mediaSource, options); + var videoRequest = state.Options; - state.OutputAudioBitrate = GetAudioBitrateParam(request, state.AudioStream); + AttachMediaSourceInfo(state, mediaSource, videoRequest); + + //var container = Path.GetExtension(state.RequestedUrl); + + //if (string.IsNullOrEmpty(container)) + //{ + // container = request.Static ? + // state.InputContainer : + // (Path.GetExtension(GetOutputFilePath(state)) ?? string.Empty).TrimStart('.'); + //} + + //state.OutputContainer = (container ?? string.Empty).TrimStart('.'); + + state.OutputAudioBitrate = GetAudioBitrateParam(state.Options, state.AudioStream); state.OutputAudioSampleRate = request.AudioSampleRate; - state.OutputAudioCodec = GetAudioCodec(request); + state.OutputAudioCodec = state.Options.AudioCodec; - state.OutputAudioChannels = GetNumAudioChannelsParam(request, state.AudioStream, state.OutputAudioCodec); + state.OutputAudioChannels = GetNumAudioChannelsParam(state.Options, state.AudioStream, state.OutputAudioCodec); - if (isVideoRequest) + if (videoRequest != null) { - state.OutputVideoCodec = GetVideoCodec(request); - state.OutputVideoBitrate = GetVideoBitrateParamValue(request, state.VideoStream); + state.OutputVideoCodec = state.Options.VideoCodec; + state.OutputVideoBitrate = GetVideoBitrateParamValue(state.Options, state.VideoStream); if (state.OutputVideoBitrate.HasValue) { var resolution = ResolutionNormalizer.Normalize( - state.VideoStream == null ? (int?)null : state.VideoStream.BitRate, - state.OutputVideoBitrate.Value, - state.VideoStream == null ? null : state.VideoStream.Codec, + state.VideoStream == null ? (int?)null : state.VideoStream.BitRate, + state.OutputVideoBitrate.Value, + state.VideoStream == null ? null : state.VideoStream.Codec, state.OutputVideoCodec, - request.MaxWidth, - request.MaxHeight); + videoRequest.MaxWidth, + videoRequest.MaxHeight); - request.MaxWidth = resolution.MaxWidth; - request.MaxHeight = resolution.MaxHeight; + videoRequest.MaxWidth = resolution.MaxWidth; + videoRequest.MaxHeight = resolution.MaxHeight; } } ApplyDeviceProfileSettings(state); - TryStreamCopy(state, request); + if (videoRequest != null) + { + TryStreamCopy(state, videoRequest); + } + + //state.OutputFilePath = GetOutputFilePath(state); return state; } @@ -119,7 +138,7 @@ namespace MediaBrowser.MediaEncoding.Encoder } } - internal static void AttachMediaStreamInfo(EncodingJob state, + internal static void AttachMediaSourceInfo(EncodingJob state, MediaSourceInfo mediaSource, EncodingJobOptions videoRequest) { @@ -131,11 +150,6 @@ namespace MediaBrowser.MediaEncoding.Encoder state.RunTimeTicks = mediaSource.RunTimeTicks; state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders; - if (mediaSource.ReadAtNativeFramerate) - { - state.ReadInputAtNativeFramerate = true; - } - if (mediaSource.VideoType.HasValue) { state.VideoType = mediaSource.VideoType.Value; @@ -156,6 +170,7 @@ namespace MediaBrowser.MediaEncoding.Encoder state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders; state.InputBitrate = mediaSource.Bitrate; state.InputFileSize = mediaSource.Size; + state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate; if (state.ReadInputAtNativeFramerate || mediaSource.Protocol == MediaProtocol.File && string.Equals(mediaSource.Container, "wtv", StringComparison.OrdinalIgnoreCase)) @@ -165,6 +180,12 @@ namespace MediaBrowser.MediaEncoding.Encoder state.InputAudioSync = "1"; } + if (string.Equals(mediaSource.Container, "wma", StringComparison.OrdinalIgnoreCase)) + { + // Seeing some stuttering when transcoding wma to audio-only HLS + state.InputAudioSync = "1"; + } + var mediaStreams = mediaSource.MediaStreams; if (videoRequest != null) @@ -210,19 +231,21 @@ namespace MediaBrowser.MediaEncoding.Encoder /// System.Nullable{VideoCodecs}. private static string InferVideoCodec(string container) { - if (string.Equals(container, "asf", StringComparison.OrdinalIgnoreCase)) + var ext = "." + (container ?? string.Empty); + + if (string.Equals(ext, ".asf", StringComparison.OrdinalIgnoreCase)) { return "wmv"; } - if (string.Equals(container, "webm", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(ext, ".webm", StringComparison.OrdinalIgnoreCase)) { return "vpx"; } - if (string.Equals(container, "ogg", StringComparison.OrdinalIgnoreCase) || string.Equals(container, "ogv", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(ext, ".ogg", StringComparison.OrdinalIgnoreCase) || string.Equals(ext, ".ogv", StringComparison.OrdinalIgnoreCase)) { return "theora"; } - if (string.Equals(container, "m3u8", StringComparison.OrdinalIgnoreCase) || string.Equals(container, "ts", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(ext, ".m3u8", StringComparison.OrdinalIgnoreCase) || string.Equals(ext, ".ts", StringComparison.OrdinalIgnoreCase)) { return "h264"; } @@ -232,35 +255,37 @@ namespace MediaBrowser.MediaEncoding.Encoder private string InferAudioCodec(string container) { - if (string.Equals(container, "mp3", StringComparison.OrdinalIgnoreCase)) + var ext = "." + (container ?? string.Empty); + + if (string.Equals(ext, ".mp3", StringComparison.OrdinalIgnoreCase)) { return "mp3"; } - if (string.Equals(container, "aac", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(ext, ".aac", StringComparison.OrdinalIgnoreCase)) { return "aac"; } - if (string.Equals(container, "wma", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(ext, ".wma", StringComparison.OrdinalIgnoreCase)) { return "wma"; } - if (string.Equals(container, "ogg", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(ext, ".ogg", StringComparison.OrdinalIgnoreCase)) { return "vorbis"; } - if (string.Equals(container, "oga", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(ext, ".oga", StringComparison.OrdinalIgnoreCase)) { return "vorbis"; } - if (string.Equals(container, "ogv", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(ext, ".ogv", StringComparison.OrdinalIgnoreCase)) { return "vorbis"; } - if (string.Equals(container, "webm", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(ext, ".webm", StringComparison.OrdinalIgnoreCase)) { return "vorbis"; } - if (string.Equals(container, "webma", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(ext, ".webma", StringComparison.OrdinalIgnoreCase)) { return "vorbis"; } @@ -398,15 +423,8 @@ namespace MediaBrowser.MediaEncoding.Encoder if (bitrate.HasValue) { - var hasFixedResolution = state.Options.HasFixedResolution; - if (string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase)) { - if (hasFixedResolution) - { - return string.Format(" -minrate:v ({0}*.90) -maxrate:v ({0}*1.10) -bufsize:v {0} -b:v {0}", bitrate.Value.ToString(UsCulture)); - } - // With vpx when crf is used, b:v becomes a max rate // https://trac.ffmpeg.org/wiki/vpxEncodingGuide. But higher bitrate source files -b:v causes judder so limite the bitrate but dont allow it to "saturate" the bitrate. So dont contrain it down just up. return string.Format(" -maxrate:v {0} -bufsize:v ({0}*2) -b:v {0}", bitrate.Value.ToString(UsCulture)); @@ -417,20 +435,15 @@ namespace MediaBrowser.MediaEncoding.Encoder return string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture)); } - // H264 - if (hasFixedResolution) + // h264 + if (isHls) { - if (isHls) - { - return string.Format(" -b:v {0} -maxrate ({0}*.80) -bufsize {0}", bitrate.Value.ToString(UsCulture)); - } - - return string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture)); + return string.Format(" -b:v {0} -maxrate {0} -bufsize {1}", + bitrate.Value.ToString(UsCulture), + (bitrate.Value * 2).ToString(UsCulture)); } - return string.Format(" -maxrate {0} -bufsize {1}", - bitrate.Value.ToString(UsCulture), - (bitrate.Value * 2).ToString(UsCulture)); + return string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture)); } return string.Empty; @@ -466,11 +479,11 @@ namespace MediaBrowser.MediaEncoding.Encoder /// /// Gets the name of the output audio codec /// - /// The request. + /// The state. /// System.String. - private string GetAudioCodec(EncodingJobOptions request) + internal static string GetAudioEncoder(EncodingJob state) { - var codec = request.AudioCodec; + var codec = state.OutputAudioCodec; if (string.Equals(codec, "aac", StringComparison.OrdinalIgnoreCase)) { @@ -489,40 +502,56 @@ namespace MediaBrowser.MediaEncoding.Encoder return "wmav2"; } - return (codec ?? string.Empty).ToLower(); + return codec.ToLower(); } /// /// Gets the name of the output video codec /// - /// The request. + /// The state. + /// The options. /// System.String. - private string GetVideoCodec(EncodingJobOptions request) + internal static string GetVideoEncoder(EncodingJob state, EncodingOptions options) { - var codec = request.VideoCodec; + var codec = state.OutputVideoCodec; - if (string.Equals(codec, "h264", StringComparison.OrdinalIgnoreCase)) + if (!string.IsNullOrEmpty(codec)) { - return "libx264"; - } - if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase) || string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)) - { - return "libx265"; - } - if (string.Equals(codec, "vpx", StringComparison.OrdinalIgnoreCase)) - { - return "libvpx"; - } - if (string.Equals(codec, "wmv", StringComparison.OrdinalIgnoreCase)) - { - return "wmv2"; - } - if (string.Equals(codec, "theora", StringComparison.OrdinalIgnoreCase)) - { - return "libtheora"; + if (string.Equals(codec, "h264", StringComparison.OrdinalIgnoreCase)) + { + return GetH264Encoder(state, options); + } + if (string.Equals(codec, "vpx", StringComparison.OrdinalIgnoreCase)) + { + return "libvpx"; + } + if (string.Equals(codec, "wmv", StringComparison.OrdinalIgnoreCase)) + { + return "wmv2"; + } + if (string.Equals(codec, "theora", StringComparison.OrdinalIgnoreCase)) + { + return "libtheora"; + } + + return codec.ToLower(); } - return (codec ?? string.Empty).ToLower(); + return "copy"; + } + + internal static string GetH264Encoder(EncodingJob state, EncodingOptions options) + { + if (string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) + { + // It's currently failing on live tv + if (state.RunTimeTicks.HasValue) + { + return "h264_qsv"; + } + } + + return "libx264"; } internal static bool CanStreamCopyVideo(EncodingJobOptions request, MediaStream videoStream) diff --git a/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs index 127145aec..9d051b38b 100644 --- a/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs @@ -21,14 +21,14 @@ namespace MediaBrowser.MediaEncoding.Encoder protected override string GetCommandLineArguments(EncodingJob state) { // Get the output codec name - var videoCodec = state.OutputVideoCodec; + var videoCodec = EncodingJobFactory.GetVideoEncoder(state, GetEncodingOptions()); var format = string.Empty; var keyFrame = string.Empty; - if (string.Equals(Path.GetExtension(state.OutputFilePath), ".mp4", StringComparison.OrdinalIgnoreCase) && - state.Options.Context == EncodingContext.Streaming) + if (string.Equals(Path.GetExtension(state.OutputFilePath), ".mp4", StringComparison.OrdinalIgnoreCase)) { + // Comparison: https://github.com/jansmolders86/mediacenterjs/blob/master/lib/transcoding/desktop.js format = " -f mp4 -movflags frag_keyframe+empty_moov"; } @@ -53,42 +53,49 @@ namespace MediaBrowser.MediaEncoding.Encoder /// Gets video arguments to pass to ffmpeg /// /// The state. - /// The video codec. + /// The video codec. /// System.String. - private string GetVideoArguments(EncodingJob state, string codec) + private string GetVideoArguments(EncodingJob state, string videoCodec) { - var args = "-codec:v:0 " + codec; + var args = "-codec:v:0 " + videoCodec; if (state.EnableMpegtsM2TsMode) { args += " -mpegts_m2ts_mode 1"; } - // See if we can save come cpu cycles by avoiding encoding - if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase)) + var isOutputMkv = string.Equals(state.Options.OutputContainer, "mkv", StringComparison.OrdinalIgnoreCase); + + if (state.RunTimeTicks.HasValue) { - return state.VideoStream != null && IsH264(state.VideoStream) && string.Equals(state.Options.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase) ? - args + " -bsf:v h264_mp4toannexb" : - args; + //args += " -copyts -avoid_negative_ts disabled -start_at_zero"; } - if (state.Options.Context == EncodingContext.Streaming) + if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase)) { - var keyFrameArg = string.Format(" -force_key_frames expr:gte(t,n_forced*{0})", - 5.ToString(UsCulture)); + if (state.VideoStream != null && IsH264(state.VideoStream) && + (string.Equals(state.Options.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase) || isOutputMkv)) + { + args += " -bsf:v h264_mp4toannexb"; + } - args += keyFrameArg; + return args; } + var keyFrameArg = string.Format(" -force_key_frames expr:gte(t,n_forced*{0})", + 5.ToString(UsCulture)); + + args += keyFrameArg; + var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream; // Add resolution params, if specified if (!hasGraphicalSubs) { - args += GetOutputSizeParam(state, codec); + args += GetOutputSizeParam(state, videoCodec); } - var qualityParam = GetVideoQualityParam(state, codec, false); + var qualityParam = GetVideoQualityParam(state, videoCodec, false); if (!string.IsNullOrEmpty(qualityParam)) { @@ -98,7 +105,7 @@ namespace MediaBrowser.MediaEncoding.Encoder // This is for internal graphical subs if (hasGraphicalSubs) { - args += GetGraphicalSubtitleParam(state, codec); + args += GetGraphicalSubtitleParam(state, videoCodec); } return args; @@ -118,11 +125,11 @@ namespace MediaBrowser.MediaEncoding.Encoder } // Get the output codec name - var codec = state.OutputAudioCodec; + var codec = EncodingJobFactory.GetAudioEncoder(state); var args = "-codec:a:0 " + codec; - if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase)) { return args; } From 2124c55169a35042395686c2e82075ed7aec3771 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 4 Feb 2016 14:31:12 -0500 Subject: [PATCH 12/27] update packaging --- MediaBrowser.WebDashboard/Api/DashboardService.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 12cd5c385..227026154 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -356,6 +356,11 @@ namespace MediaBrowser.WebDashboard.Api DeleteFoldersByName(Path.Combine(bowerPath, "Sortable"), "st"); DeleteFoldersByName(Path.Combine(bowerPath, "Swiper"), "src"); + _fileSystem.DeleteDirectory(Path.Combine(bowerPath, "marked"), true); + _fileSystem.DeleteDirectory(Path.Combine(bowerPath, "marked-element"), true); + _fileSystem.DeleteDirectory(Path.Combine(bowerPath, "prism"), true); + _fileSystem.DeleteDirectory(Path.Combine(bowerPath, "prism-element"), true); + if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase)) { // Delete things that are unneeded in an attempt to keep the output as trim as possible From 3a059971dba0a46a0858aaad296f86453c5e8a8c Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 4 Feb 2016 15:51:13 -0500 Subject: [PATCH 13/27] use shared prompt --- MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj | 3 --- 1 file changed, 3 deletions(-) diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index 165792291..0455fd209 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -161,9 +161,6 @@ PreserveNewest - - PreserveNewest - PreserveNewest From dd5f8f0794adfc65f8618cfdf45cddcec616aea8 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 4 Feb 2016 22:44:44 -0500 Subject: [PATCH 14/27] add recording logging --- .../LiveTv/EmbyTV/EmbyTV.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index cd91684ce..ee1614104 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -742,7 +742,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV try { - var result = await GetChannelStreamInternal(timer.ChannelId, null, CancellationToken.None); + var result = await GetChannelStreamInternal(timer.ChannelId, null, CancellationToken.None).ConfigureAwait(false); var mediaStreamInfo = result.Item1; var isResourceOpen = true; @@ -771,14 +771,19 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV var linkedToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token; httpRequestOptions.CancellationToken = linkedToken; _logger.Info("Writing file to path: " + recordPath); - using (var response = await _httpClient.SendAsync(httpRequestOptions, "GET")) + _logger.Info("Opening recording stream from tuner provider"); + using (var response = await _httpClient.SendAsync(httpRequestOptions, "GET").ConfigureAwait(false)) { + _logger.Info("Opened recording stream from tuner provider"); + using (var output = _fileSystem.GetFileStream(recordPath, FileMode.Create, FileAccess.Write, FileShare.Read)) { result.Item2.Release(); isResourceOpen = false; - await response.Content.CopyToAsync(output, StreamDefaults.DefaultCopyToBufferSize, linkedToken); + _logger.Info("Copying recording stream to file stream"); + + await response.Content.CopyToAsync(output, StreamDefaults.DefaultCopyToBufferSize, linkedToken).ConfigureAwait(false); } } From 0b7d024afd5c539251a1a51b712d9d839771d8e3 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 5 Feb 2016 00:59:09 -0500 Subject: [PATCH 15/27] support codec intros --- .../Configuration/CinemaModeConfiguration.cs | 2 - .../Intros/DefaultIntroProvider.cs | 142 +++++++++++++++++- 2 files changed, 139 insertions(+), 5 deletions(-) diff --git a/MediaBrowser.Model/Configuration/CinemaModeConfiguration.cs b/MediaBrowser.Model/Configuration/CinemaModeConfiguration.cs index dd6c77e66..c4b96ea2e 100644 --- a/MediaBrowser.Model/Configuration/CinemaModeConfiguration.cs +++ b/MediaBrowser.Model/Configuration/CinemaModeConfiguration.cs @@ -16,14 +16,12 @@ namespace MediaBrowser.Model.Configuration public bool EnableIntrosFromUpcomingStreamingMovies { get; set; } public int TrailerLimit { get; set; } - public string[] Tags { get; set; } public CinemaModeConfiguration() { EnableIntrosParentalControl = true; EnableIntrosFromSimilarMovies = true; TrailerLimit = 2; - Tags = new[] { "thx" }; } } } diff --git a/MediaBrowser.Server.Implementations/Intros/DefaultIntroProvider.cs b/MediaBrowser.Server.Implementations/Intros/DefaultIntroProvider.cs index edc329ec4..5b72860b6 100644 --- a/MediaBrowser.Server.Implementations/Intros/DefaultIntroProvider.cs +++ b/MediaBrowser.Server.Implementations/Intros/DefaultIntroProvider.cs @@ -17,6 +17,7 @@ using System.Threading; using System.Threading.Tasks; using CommonIO; using MediaBrowser.Common.IO; +using MoreLinq; namespace MediaBrowser.Server.Implementations.Intros { @@ -28,8 +29,9 @@ namespace MediaBrowser.Server.Implementations.Intros private readonly IConfigurationManager _serverConfig; private readonly ILibraryManager _libraryManager; private readonly IFileSystem _fileSystem; + private readonly IMediaSourceManager _mediaSourceManager; - public DefaultIntroProvider(ISecurityManager security, IChannelManager channelManager, ILocalizationManager localization, IConfigurationManager serverConfig, ILibraryManager libraryManager, IFileSystem fileSystem) + public DefaultIntroProvider(ISecurityManager security, IChannelManager channelManager, ILocalizationManager localization, IConfigurationManager serverConfig, ILibraryManager libraryManager, IFileSystem fileSystem, IMediaSourceManager mediaSourceManager) { _security = security; _channelManager = channelManager; @@ -37,6 +39,7 @@ namespace MediaBrowser.Server.Implementations.Intros _serverConfig = serverConfig; _libraryManager = libraryManager; _fileSystem = fileSystem; + _mediaSourceManager = mediaSourceManager; } public async Task> GetIntros(BaseItem item, User user) @@ -82,7 +85,7 @@ namespace MediaBrowser.Server.Implementations.Intros { IncludeItemTypes = new[] { typeof(Movie).Name } - }, new string[]{}); + }, new string[] { }); var itemsWithTrailers = inputItems .Where(i => @@ -164,6 +167,10 @@ namespace MediaBrowser.Server.Implementations.Intros GetCustomIntros(config) : new List(); + var mediaInfoIntros = !string.IsNullOrWhiteSpace(config.MediaInfoIntroPath) ? + GetMediaInfoIntros(config, item) : + new List(); + var trailerLimit = config.TrailerLimit; // Avoid implicitly captured closure @@ -185,7 +192,8 @@ namespace MediaBrowser.Server.Implementations.Intros .ThenByDescending(i => (i.IsPlayed ? 0 : 1)) .Select(i => i.IntroInfo) .Take(trailerLimit) - .Concat(customIntros.Take(1)); + .Concat(customIntros.Take(1)) + .Concat(mediaInfoIntros); } private bool IsDuplicate(BaseItem playingContent, BaseItem test) @@ -228,6 +236,134 @@ namespace MediaBrowser.Server.Implementations.Intros } } + private IEnumerable GetMediaInfoIntros(CinemaModeConfiguration options, BaseItem item) + { + try + { + var hasMediaSources = item as IHasMediaSources; + + if (hasMediaSources == null) + { + return new List(); + } + + var mediaSource = _mediaSourceManager.GetStaticMediaSources(hasMediaSources, false) + .FirstOrDefault(); + + if (mediaSource == null) + { + return new List(); + } + + var videoStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video); + var audioStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio); + + var allIntros = GetCustomIntroFiles(options, false, true) + .OrderBy(i => Guid.NewGuid()) + .Select(i => new IntroInfo + { + Path = i + + }).ToList(); + + var returnResult = new List(); + + if (videoStream != null) + { + returnResult.AddRange(GetMediaInfoIntrosByVideoStream(allIntros, videoStream).Take(1)); + } + + if (audioStream != null) + { + returnResult.AddRange(GetMediaInfoIntrosByAudioStream(allIntros, audioStream).Take(1)); + } + + returnResult.AddRange(GetMediaInfoIntrosByTags(allIntros, item.Tags).Take(1)); + + return returnResult.DistinctBy(i => i.Path, StringComparer.OrdinalIgnoreCase); + } + catch (IOException) + { + return new List(); + } + } + + private IEnumerable GetMediaInfoIntrosByVideoStream(List allIntros, MediaStream stream) + { + var codec = stream.Codec; + + if (string.IsNullOrWhiteSpace(codec)) + { + return new List(); + } + + return allIntros + .Where(i => IsMatch(i.Path, codec)); + } + + private IEnumerable GetMediaInfoIntrosByAudioStream(List allIntros, MediaStream stream) + { + var codec = stream.Codec; + + if (string.IsNullOrWhiteSpace(codec)) + { + return new List(); + } + + return allIntros + .Where(i => IsAudioMatch(i.Path, stream)); + } + + private IEnumerable GetMediaInfoIntrosByTags(List allIntros, List tags) + { + return allIntros + .Where(i => tags.Any(t => IsMatch(i.Path, t))); + } + + private bool IsMatch(string file, string attribute) + { + var filename = Path.GetFileNameWithoutExtension(file) ?? string.Empty; + filename = Normalize(filename); + + if (string.IsNullOrWhiteSpace(filename)) + { + return false; + } + + attribute = Normalize(attribute); + if (string.IsNullOrWhiteSpace(attribute)) + { + return false; + } + + return string.Equals(filename, attribute, StringComparison.OrdinalIgnoreCase); + } + + private string Normalize(string value) + { + return value; + } + + private bool IsAudioMatch(string path, MediaStream stream) + { + if (!string.IsNullOrWhiteSpace(stream.Codec)) + { + if (IsMatch(path, stream.Codec)) + { + return true; + } + } + if (!string.IsNullOrWhiteSpace(stream.Profile)) + { + if (IsMatch(path, stream.Profile)) + { + return true; + } + } + + return false; + } + private IEnumerable GetCustomIntroFiles(CinemaModeConfiguration options, bool enableCustomIntros, bool enableMediaInfoIntros) { var list = new List(); From b9c8c4e34655362740c0f50486d3e44d31255264 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 5 Feb 2016 00:59:22 -0500 Subject: [PATCH 16/27] fix issue with intros before being scanned --- MediaBrowser.Server.Implementations/Library/LibraryManager.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index df0f0045e..e4cb58346 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -1536,6 +1536,10 @@ namespace MediaBrowser.Server.Implementations.Library { video = dbItem; } + else + { + return null; + } } } catch (Exception ex) From ee274b03698cea957cea5f0dd6281ae632617704 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 5 Feb 2016 00:59:33 -0500 Subject: [PATCH 17/27] restore legacy folder --- MediaBrowser.Providers/Manager/ImageSaver.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs index 32ea47425..683bb0f60 100644 --- a/MediaBrowser.Providers/Manager/ImageSaver.cs +++ b/MediaBrowser.Providers/Manager/ImageSaver.cs @@ -375,7 +375,7 @@ namespace MediaBrowser.Providers.Manager } string filename; - var folderName = item is MusicAlbum || item is MusicArtist ? "folder" : "poster"; + var folderName = item is MusicAlbum || item is MusicArtist || (_config.Configuration.ImageSavingConvention == ImageSavingConvention.Legacy && saveLocally) ? "folder" : "poster"; switch (type) { From f9260a99d6f99a4bc10f16dcf7e5849da6d6062e Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 5 Feb 2016 01:03:41 -0500 Subject: [PATCH 18/27] update translations --- MediaBrowser.Server.Implementations/Localization/Core/de.json | 2 +- .../Localization/Core/es-MX.json | 2 +- MediaBrowser.Server.Implementations/Localization/Core/fr.json | 2 +- MediaBrowser.Server.Implementations/Localization/Core/kk.json | 2 +- .../Localization/Core/pt-BR.json | 2 +- MediaBrowser.Server.Implementations/Localization/Core/ru.json | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/MediaBrowser.Server.Implementations/Localization/Core/de.json b/MediaBrowser.Server.Implementations/Localization/Core/de.json index f948bc3dc..30e3d9215 100644 --- a/MediaBrowser.Server.Implementations/Localization/Core/de.json +++ b/MediaBrowser.Server.Implementations/Localization/Core/de.json @@ -1,5 +1,5 @@ { - "DbUpgradeMessage": "Please wait while your Emby Server database is upgraded. {0}% complete.", + "DbUpgradeMessage": "Bitte warten Sie w\u00e4hrend die Emby Datenbank aktualisiert wird. {0}% verarbeitet.", "AppDeviceValues": "App: {0}, Ger\u00e4t: {1}", "UserDownloadingItemWithValues": "{0} l\u00e4dt {1} herunter", "FolderTypeMixed": "Gemischte Inhalte", diff --git a/MediaBrowser.Server.Implementations/Localization/Core/es-MX.json b/MediaBrowser.Server.Implementations/Localization/Core/es-MX.json index c510f30e2..630c7a037 100644 --- a/MediaBrowser.Server.Implementations/Localization/Core/es-MX.json +++ b/MediaBrowser.Server.Implementations/Localization/Core/es-MX.json @@ -1,5 +1,5 @@ { - "DbUpgradeMessage": "Please wait while your Emby Server database is upgraded. {0}% complete.", + "DbUpgradeMessage": "Por favor espere mientras la base de datos de su Servidor Emby es actualizada. {0}% completo.", "AppDeviceValues": "App: {0}, Dispositivo: {1}", "UserDownloadingItemWithValues": "{0} esta descargando {1}", "FolderTypeMixed": "Contenido mezclado", diff --git a/MediaBrowser.Server.Implementations/Localization/Core/fr.json b/MediaBrowser.Server.Implementations/Localization/Core/fr.json index 2ef9fc53c..25c722989 100644 --- a/MediaBrowser.Server.Implementations/Localization/Core/fr.json +++ b/MediaBrowser.Server.Implementations/Localization/Core/fr.json @@ -1,5 +1,5 @@ { - "DbUpgradeMessage": "Please wait while your Emby Server database is upgraded. {0}% complete.", + "DbUpgradeMessage": "Veuillez patienter pendant que la base de donn\u00e9e de votre Emby Serveur se met \u00e0 jour. Termin\u00e9e \u00e0 {0}%.", "AppDeviceValues": "Application : {0}, Appareil: {1}", "UserDownloadingItemWithValues": "{0} est en train de t\u00e9l\u00e9charger {1}", "FolderTypeMixed": "Contenus m\u00e9lang\u00e9s", diff --git a/MediaBrowser.Server.Implementations/Localization/Core/kk.json b/MediaBrowser.Server.Implementations/Localization/Core/kk.json index 654287d45..93252c30b 100644 --- a/MediaBrowser.Server.Implementations/Localization/Core/kk.json +++ b/MediaBrowser.Server.Implementations/Localization/Core/kk.json @@ -1,5 +1,5 @@ { - "DbUpgradeMessage": "Please wait while your Emby Server database is upgraded. {0}% complete.", + "DbUpgradeMessage": "Emby Server \u0434\u0435\u0440\u0435\u043a\u049b\u043e\u0440\u044b\u04a3\u044b\u0437\u0434\u044b\u04a3 \u0436\u0430\u04a3\u0493\u044b\u0440\u0442\u044b\u043b\u0443\u044b\u043d \u043a\u04af\u0442\u0435 \u0442\u04b1\u0440\u044b\u04a3\u044b\u0437. {0} % \u0430\u044f\u049b\u0442\u0430\u043b\u0434\u044b.", "AppDeviceValues": "\u049a\u043e\u043b\u0434\u0430\u043d\u0431\u0430: {0}, \u049a\u04b1\u0440\u044b\u043b\u0493\u044b: {1}", "UserDownloadingItemWithValues": "{0} \u043c\u044b\u043d\u0430\u043d\u044b \u0436\u04af\u043a\u0442\u0435\u043f \u0430\u043b\u0443\u0434\u0430: {1}", "FolderTypeMixed": "\u0410\u0440\u0430\u043b\u0430\u0441 \u043c\u0430\u0437\u043c\u04b1\u043d", diff --git a/MediaBrowser.Server.Implementations/Localization/Core/pt-BR.json b/MediaBrowser.Server.Implementations/Localization/Core/pt-BR.json index a2d5ccf61..b714fd44c 100644 --- a/MediaBrowser.Server.Implementations/Localization/Core/pt-BR.json +++ b/MediaBrowser.Server.Implementations/Localization/Core/pt-BR.json @@ -1,5 +1,5 @@ { - "DbUpgradeMessage": "Please wait while your Emby Server database is upgraded. {0}% complete.", + "DbUpgradeMessage": "Por favor, aguarde enquanto a base de dados do Servidor Emby \u00e9 atualizada. {0}% completado.", "AppDeviceValues": "App: {0}, Dispositivo: {1}", "UserDownloadingItemWithValues": "{0} est\u00e1 fazendo download de {1}", "FolderTypeMixed": "Conte\u00fado misto", diff --git a/MediaBrowser.Server.Implementations/Localization/Core/ru.json b/MediaBrowser.Server.Implementations/Localization/Core/ru.json index 6f2997ffa..c5ff0c134 100644 --- a/MediaBrowser.Server.Implementations/Localization/Core/ru.json +++ b/MediaBrowser.Server.Implementations/Localization/Core/ru.json @@ -1,5 +1,5 @@ { - "DbUpgradeMessage": "Please wait while your Emby Server database is upgraded. {0}% complete.", + "DbUpgradeMessage": "\u041f\u043e\u0434\u043e\u0436\u0434\u0438\u0442\u0435, \u043f\u043e\u043a\u0430 \u0432\u0430\u0448\u0430 \u0431\u0430\u0437\u0430 \u0434\u0430\u043d\u043d\u044b\u0445 \u043d\u0430 Emby Server \u043c\u043e\u0434\u0435\u0440\u043d\u0438\u0437\u0438\u0440\u0443\u0435\u0442\u0441\u044f. {0} % \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043e.", "AppDeviceValues": "\u041f\u0440\u0438\u043b.: {0}, \u0423\u0441\u0442\u0440.: {1}", "UserDownloadingItemWithValues": "{0} \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u0442 {1}", "FolderTypeMixed": "\u0420\u0430\u0437\u043d\u043e\u0442\u0438\u043f\u043d\u043e\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435", From bd5bad4f8863e1c409e0efcba7fedeca43f9190b Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 5 Feb 2016 09:32:05 -0500 Subject: [PATCH 19/27] update translations --- MediaBrowser.Server.Implementations/Localization/Core/ru.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Server.Implementations/Localization/Core/ru.json b/MediaBrowser.Server.Implementations/Localization/Core/ru.json index c5ff0c134..0aacc5362 100644 --- a/MediaBrowser.Server.Implementations/Localization/Core/ru.json +++ b/MediaBrowser.Server.Implementations/Localization/Core/ru.json @@ -1,5 +1,5 @@ { - "DbUpgradeMessage": "\u041f\u043e\u0434\u043e\u0436\u0434\u0438\u0442\u0435, \u043f\u043e\u043a\u0430 \u0432\u0430\u0448\u0430 \u0431\u0430\u0437\u0430 \u0434\u0430\u043d\u043d\u044b\u0445 \u043d\u0430 Emby Server \u043c\u043e\u0434\u0435\u0440\u043d\u0438\u0437\u0438\u0440\u0443\u0435\u0442\u0441\u044f. {0} % \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043e.", + "DbUpgradeMessage": "\u041f\u043e\u0434\u043e\u0436\u0434\u0438\u0442\u0435, \u043f\u043e\u043a\u0430 \u0431\u0430\u0437\u0430 \u0434\u0430\u043d\u043d\u044b\u0445 \u043d\u0430 \u0432\u0430\u0448\u0435\u043c Emby Server \u043c\u043e\u0434\u0435\u0440\u043d\u0438\u0437\u0438\u0440\u0443\u0435\u0442\u0441\u044f. {0} % \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043e.", "AppDeviceValues": "\u041f\u0440\u0438\u043b.: {0}, \u0423\u0441\u0442\u0440.: {1}", "UserDownloadingItemWithValues": "{0} \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u0442 {1}", "FolderTypeMixed": "\u0420\u0430\u0437\u043d\u043e\u0442\u0438\u043f\u043d\u043e\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435", From 4ba4072e63022436899ef941c0b7d48f6a4198fc Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 5 Feb 2016 10:31:11 -0500 Subject: [PATCH 20/27] add credentials to media elements --- MediaBrowser.Model/Dto/MediaSourceInfo.cs | 3 +++ .../Sync/SyncedMediaSourceProvider.cs | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/MediaBrowser.Model/Dto/MediaSourceInfo.cs b/MediaBrowser.Model/Dto/MediaSourceInfo.cs index 8897edcbd..c794db249 100644 --- a/MediaBrowser.Model/Dto/MediaSourceInfo.cs +++ b/MediaBrowser.Model/Dto/MediaSourceInfo.cs @@ -52,6 +52,8 @@ namespace MediaBrowser.Model.Dto public string TranscodingSubProtocol { get; set; } public string TranscodingContainer { get; set; } + public bool EnableHttpCredentials { get; set; } + public MediaSourceInfo() { Formats = new List(); @@ -61,6 +63,7 @@ namespace MediaBrowser.Model.Dto SupportsTranscoding = true; SupportsDirectStream = true; SupportsDirectPlay = true; + EnableHttpCredentials = true; } public int? DefaultAudioStreamIndex { get; set; } diff --git a/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs b/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs index efd37fa00..d45db6c7e 100644 --- a/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs +++ b/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs @@ -11,6 +11,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Model.MediaInfo; namespace MediaBrowser.Server.Implementations.Sync { @@ -137,6 +138,11 @@ namespace MediaBrowser.Server.Implementations.Sync mediaSource.Protocol = dynamicInfo.Protocol; mediaSource.RequiredHttpHeaders = dynamicInfo.RequiredHttpHeaders; + if (mediaSource.Protocol == MediaProtocol.Http) + { + mediaSource.EnableHttpCredentials = false; + } + return mediaSource; } From b94dc9d31a64eaf74be8d98ae64de0d81a219e63 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 5 Feb 2016 11:04:04 -0500 Subject: [PATCH 21/27] revert origin change --- MediaBrowser.Model/Dto/MediaSourceInfo.cs | 3 --- .../Sync/SyncedMediaSourceProvider.cs | 5 ----- 2 files changed, 8 deletions(-) diff --git a/MediaBrowser.Model/Dto/MediaSourceInfo.cs b/MediaBrowser.Model/Dto/MediaSourceInfo.cs index c794db249..f09a8b3a7 100644 --- a/MediaBrowser.Model/Dto/MediaSourceInfo.cs +++ b/MediaBrowser.Model/Dto/MediaSourceInfo.cs @@ -51,8 +51,6 @@ namespace MediaBrowser.Model.Dto public string TranscodingUrl { get; set; } public string TranscodingSubProtocol { get; set; } public string TranscodingContainer { get; set; } - - public bool EnableHttpCredentials { get; set; } public MediaSourceInfo() { @@ -63,7 +61,6 @@ namespace MediaBrowser.Model.Dto SupportsTranscoding = true; SupportsDirectStream = true; SupportsDirectPlay = true; - EnableHttpCredentials = true; } public int? DefaultAudioStreamIndex { get; set; } diff --git a/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs b/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs index d45db6c7e..456a1fcc1 100644 --- a/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs +++ b/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs @@ -138,11 +138,6 @@ namespace MediaBrowser.Server.Implementations.Sync mediaSource.Protocol = dynamicInfo.Protocol; mediaSource.RequiredHttpHeaders = dynamicInfo.RequiredHttpHeaders; - if (mediaSource.Protocol == MediaProtocol.Http) - { - mediaSource.EnableHttpCredentials = false; - } - return mediaSource; } From 32013f5ae50950f5da2f3113b5fb12a943a83c16 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 5 Feb 2016 11:04:18 -0500 Subject: [PATCH 22/27] deprecate merge setting --- MediaBrowser.Api/StartupWizardService.cs | 1 - .../Configuration/ServerConfiguration.cs | 2 -- .../Configuration/ServerConfigurationManager.cs | 15 --------------- 3 files changed, 18 deletions(-) diff --git a/MediaBrowser.Api/StartupWizardService.cs b/MediaBrowser.Api/StartupWizardService.cs index 6556807c4..06db1de74 100644 --- a/MediaBrowser.Api/StartupWizardService.cs +++ b/MediaBrowser.Api/StartupWizardService.cs @@ -68,7 +68,6 @@ namespace MediaBrowser.Api _config.Configuration.EnableLocalizedGuids = true; _config.Configuration.EnableCustomPathSubFolders = true; _config.Configuration.EnableDateLastRefresh = true; - _config.Configuration.MergeMetadataAndImagesByName = true; _config.SaveConfiguration(); } diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 0b472ec22..3cb543e5d 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -161,8 +161,6 @@ namespace MediaBrowser.Model.Configuration /// /// The dashboard source path. public string DashboardSourcePath { get; set; } - - public bool MergeMetadataAndImagesByName { get; set; } /// /// Gets or sets the image saving convention. diff --git a/MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs b/MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs index 8e934f348..1a5c35df8 100644 --- a/MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs +++ b/MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs @@ -35,7 +35,6 @@ namespace MediaBrowser.Server.Implementations.Configuration public ServerConfigurationManager(IApplicationPaths applicationPaths, ILogManager logManager, IXmlSerializer xmlSerializer, IFileSystem fileSystem) : base(applicationPaths, logManager, xmlSerializer, fileSystem) { - UpdateItemsByNamePath(); UpdateMetadataPath(); } @@ -73,7 +72,6 @@ namespace MediaBrowser.Server.Implementations.Configuration /// protected override void OnConfigurationUpdated() { - UpdateItemsByNamePath(); UpdateMetadataPath(); base.OnConfigurationUpdated(); @@ -86,19 +84,6 @@ namespace MediaBrowser.Server.Implementations.Configuration UpdateTranscodingTempPath(); } - /// - /// Updates the items by name path. - /// - private void UpdateItemsByNamePath() - { - if (!Configuration.MergeMetadataAndImagesByName) - { - ((ServerApplicationPaths)ApplicationPaths).ItemsByNamePath = string.IsNullOrEmpty(Configuration.ItemsByNamePath) ? - null : - Configuration.ItemsByNamePath; - } - } - /// /// Updates the metadata path. /// From 16145380d90edb77f82e596e89ce98160327842c Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 5 Feb 2016 12:04:38 -0500 Subject: [PATCH 23/27] update android init --- MediaBrowser.Api/UserLibrary/ItemsService.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs index 1c5d5b345..761d10760 100644 --- a/MediaBrowser.Api/UserLibrary/ItemsService.cs +++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs @@ -116,6 +116,10 @@ namespace MediaBrowser.Api.UserLibrary { item = user == null ? _libraryManager.RootFolder : user.RootFolder; } + else if (string.Equals(request.IncludeItemTypes, "BoxSet", StringComparison.OrdinalIgnoreCase)) + { + item = user == null ? _libraryManager.RootFolder : user.RootFolder; + } // Default list type = children From 78a90a48846fee6b57069f0bbc096048fac2bc39 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 5 Feb 2016 21:13:36 -0500 Subject: [PATCH 24/27] save photo album images as folder.ext --- MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs | 2 +- MediaBrowser.Providers/Manager/ImageSaver.cs | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs index c6f81d874..0bbe3f38a 100644 --- a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs +++ b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs @@ -176,7 +176,7 @@ namespace MediaBrowser.LocalMetadata.Images "default" }; - if (item is MusicAlbum || item is MusicArtist) + if (item is MusicAlbum || item is MusicArtist || item is Photo) { // these prefer folder names.Insert(0, "poster"); diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs index 683bb0f60..64a0e6d5b 100644 --- a/MediaBrowser.Providers/Manager/ImageSaver.cs +++ b/MediaBrowser.Providers/Manager/ImageSaver.cs @@ -375,7 +375,12 @@ namespace MediaBrowser.Providers.Manager } string filename; - var folderName = item is MusicAlbum || item is MusicArtist || (_config.Configuration.ImageSavingConvention == ImageSavingConvention.Legacy && saveLocally) ? "folder" : "poster"; + var folderName = item is MusicAlbum || + item is MusicArtist || + item is PhotoAlbum || + (saveLocally && _config.Configuration.ImageSavingConvention == ImageSavingConvention.Legacy) ? + "folder" : + "poster"; switch (type) { From 785b20b48c8af035822421f0bce442d4e40b8cfe Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 5 Feb 2016 21:57:35 -0500 Subject: [PATCH 25/27] update project --- MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj | 3 +++ 1 file changed, 3 insertions(+) diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index 0455fd209..efb2d4b69 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -107,6 +107,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest From 60147b5a0b477f56cae40cd4a30b0b900d252fa3 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 5 Feb 2016 22:06:05 -0500 Subject: [PATCH 26/27] update translations --- MediaBrowser.Server.Implementations/Localization/Core/nl.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Server.Implementations/Localization/Core/nl.json b/MediaBrowser.Server.Implementations/Localization/Core/nl.json index 053382114..a83182ee8 100644 --- a/MediaBrowser.Server.Implementations/Localization/Core/nl.json +++ b/MediaBrowser.Server.Implementations/Localization/Core/nl.json @@ -1,5 +1,5 @@ { - "DbUpgradeMessage": "Please wait while your Emby Server database is upgraded. {0}% complete.", + "DbUpgradeMessage": "Even geduld svp terwijl de Emby Server database ge-upgrade wordt. {0}% gereed.", "AppDeviceValues": "App: {0}, Apparaat: {1}", "UserDownloadingItemWithValues": "{0} download {1}", "FolderTypeMixed": "Gemengde inhoud", From 7236ce0f92b684f1759b3fc272348b411dcf7c6b Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 5 Feb 2016 23:39:10 -0500 Subject: [PATCH 27/27] ensure server reports consistent uuid --- MediaBrowser.Dlna/Server/DescriptionXmlBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Dlna/Server/DescriptionXmlBuilder.cs b/MediaBrowser.Dlna/Server/DescriptionXmlBuilder.cs index 386370596..281dee3e0 100644 --- a/MediaBrowser.Dlna/Server/DescriptionXmlBuilder.cs +++ b/MediaBrowser.Dlna/Server/DescriptionXmlBuilder.cs @@ -122,7 +122,7 @@ namespace MediaBrowser.Dlna.Server builder.Append("" + SecurityElement.Escape(_profile.SerialNumber) + ""); } - builder.Append("uuid:" + SecurityElement.Escape(_serverUdn) + ""); + builder.Append("uuid:" + SecurityElement.Escape(_serverId) + ""); builder.Append("" + SecurityElement.Escape(_serverAddress) + ""); if (!EnableAbsoluteUrls)