summaryrefslogtreecommitdiffstats
path: root/win/CS
diff options
context:
space:
mode:
authorScott <[email protected]>2020-04-29 19:06:11 +0100
committerGitHub <[email protected]>2020-04-29 19:06:11 +0100
commit91051b41df7f9e6da68d14c9e806968df61ef050 (patch)
tree90293ad067e14e9eff409ed7bf5d105df3697a45 /win/CS
parent565dae9f71330b87c5e7898a469052446c4592f0 (diff)
WinGui: Enable multi-instance support. (#2797)
Diffstat (limited to 'win/CS')
-rw-r--r--win/CS/HandBrake.Interop/Interop/HandBrakeInstance.cs12
-rw-r--r--win/CS/HandBrake.Interop/Interop/Interfaces/IEncodeInstance.cs2
-rw-r--r--win/CS/HandBrakeWPF/Instance/HandBrakeInstanceManager.cs21
-rw-r--r--win/CS/HandBrakeWPF/Instance/RemoteInstance.cs60
-rw-r--r--win/CS/HandBrakeWPF/Properties/Resources.Designer.cs18
-rw-r--r--win/CS/HandBrakeWPF/Properties/Resources.de.resx3
-rw-r--r--win/CS/HandBrakeWPF/Properties/Resources.es.resx3
-rw-r--r--win/CS/HandBrakeWPF/Properties/Resources.fr.resx3
-rw-r--r--win/CS/HandBrakeWPF/Properties/Resources.ja.resx3
-rw-r--r--win/CS/HandBrakeWPF/Properties/Resources.ko.resx3
-rw-r--r--win/CS/HandBrakeWPF/Properties/Resources.resx6
-rw-r--r--win/CS/HandBrakeWPF/Properties/Resources.ru.resx3
-rw-r--r--win/CS/HandBrakeWPF/Properties/Resources.tr.resx3
-rw-r--r--win/CS/HandBrakeWPF/Properties/Resources.zh.resx3
-rw-r--r--win/CS/HandBrakeWPF/Services/Encode/LibEncode.cs26
-rw-r--r--win/CS/HandBrakeWPF/Services/Logging/LogInstanceManager.cs64
-rw-r--r--win/CS/HandBrakeWPF/Services/Queue/Interfaces/IQueueService.cs5
-rw-r--r--win/CS/HandBrakeWPF/Services/Queue/QueueService.cs65
-rw-r--r--win/CS/HandBrakeWPF/ViewModels/MainViewModel.cs37
-rw-r--r--win/CS/HandBrakeWPF/ViewModels/QueueViewModel.cs3
-rw-r--r--win/CS/HandBrakeWPF/ViewModels/StaticPreviewViewModel.cs64
-rw-r--r--win/CS/HandBrakeWPF/Views/MainView.xaml4
-rw-r--r--win/CS/HandBrakeWPF/Views/OptionsView.xaml3
-rw-r--r--win/CS/HandBrakeWPF/Views/ShellView.xaml4
-rw-r--r--win/CS/HandBrakeWPF/Views/StaticPreviewView.xaml4
25 files changed, 226 insertions, 196 deletions
diff --git a/win/CS/HandBrake.Interop/Interop/HandBrakeInstance.cs b/win/CS/HandBrake.Interop/Interop/HandBrakeInstance.cs
index a5027f72f..5d4dd98ac 100644
--- a/win/CS/HandBrake.Interop/Interop/HandBrakeInstance.cs
+++ b/win/CS/HandBrake.Interop/Interop/HandBrakeInstance.cs
@@ -13,6 +13,7 @@ namespace HandBrake.Interop.Interop
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
+ using System.Runtime.CompilerServices;
using System.Runtime.ExceptionServices;
using System.Runtime.InteropServices;
using System.Timers;
@@ -73,10 +74,6 @@ namespace HandBrake.Interop.Interop
/// </summary>
public event EventHandler<EncodeCompletedEventArgs> EncodeCompleted;
- /// <summary>
- /// Gets the handle.
- /// </summary>
- internal IntPtr Handle { get; private set; }
/// <summary>
/// Gets the number of previews created during scan.
@@ -108,6 +105,13 @@ namespace HandBrake.Interop.Interop
/// </summary>
public int Build => hbFunctions.hb_get_build(this.Handle);
+ public bool IsRemoteInstance => false;
+
+ /// <summary>
+ /// Gets the handle.
+ /// </summary>
+ internal IntPtr Handle { get; private set; }
+
/// <summary>
/// Initializes this instance.
/// </summary>
diff --git a/win/CS/HandBrake.Interop/Interop/Interfaces/IEncodeInstance.cs b/win/CS/HandBrake.Interop/Interop/Interfaces/IEncodeInstance.cs
index 5a0957cb9..e7a52fbfa 100644
--- a/win/CS/HandBrake.Interop/Interop/Interfaces/IEncodeInstance.cs
+++ b/win/CS/HandBrake.Interop/Interop/Interfaces/IEncodeInstance.cs
@@ -27,6 +27,8 @@ namespace HandBrake.Interop.Interop.Interfaces
/// </summary>
event EventHandler<EncodeProgressEventArgs> EncodeProgress;
+ bool IsRemoteInstance { get; }
+
/// <summary>
/// Initializes this instance.
/// </summary>
diff --git a/win/CS/HandBrakeWPF/Instance/HandBrakeInstanceManager.cs b/win/CS/HandBrakeWPF/Instance/HandBrakeInstanceManager.cs
index a343a1afd..51ac11458 100644
--- a/win/CS/HandBrakeWPF/Instance/HandBrakeInstanceManager.cs
+++ b/win/CS/HandBrakeWPF/Instance/HandBrakeInstanceManager.cs
@@ -44,12 +44,6 @@ namespace HandBrakeWPF.Instance
throw new Exception("Please call Init before Using!");
}
- if (encodeInstance != null)
- {
- encodeInstance.Dispose();
- encodeInstance = null;
- }
-
IEncodeInstance newInstance;
if (userSettingService.GetUserSetting<bool>(UserSettingConstants.ProcessIsolationEnabled) && Portable.IsProcessIsolationEnabled())
@@ -58,16 +52,19 @@ namespace HandBrakeWPF.Instance
}
else
{
+ if (encodeInstance != null && !encodeInstance.IsRemoteInstance)
+ {
+ encodeInstance.Dispose();
+ encodeInstance = null;
+ }
+
newInstance = new HandBrakeInstance();
+ HandBrakeUtils.SetDvdNav(!userSettingService.GetUserSetting<bool>(UserSettingConstants.DisableLibDvdNav));
+ encodeInstance = newInstance;
}
newInstance.Initialize(verbosity, noHardware);
-
- encodeInstance = newInstance;
-
- HandBrakeUtils.SetDvdNav(!userSettingService.GetUserSetting<bool>(UserSettingConstants.DisableLibDvdNav));
-
- return encodeInstance;
+ return newInstance;
}
/// <summary>
diff --git a/win/CS/HandBrakeWPF/Instance/RemoteInstance.cs b/win/CS/HandBrakeWPF/Instance/RemoteInstance.cs
index c2bae9c85..97abf671b 100644
--- a/win/CS/HandBrakeWPF/Instance/RemoteInstance.cs
+++ b/win/CS/HandBrakeWPF/Instance/RemoteInstance.cs
@@ -31,6 +31,8 @@ namespace HandBrakeWPF.Instance
using HandBrake.Worker.Routing.Commands;
using HandBrakeWPF.Instance.Model;
+ using HandBrakeWPF.Model.Options;
+ using HandBrakeWPF.Services;
using HandBrakeWPF.Services.Interfaces;
using HandBrakeWPF.Services.Logging.Interfaces;
using HandBrakeWPF.Utilities;
@@ -62,6 +64,8 @@ namespace HandBrakeWPF.Instance
public event EventHandler<EncodeProgressEventArgs> EncodeProgress;
+ public bool IsRemoteInstance => true;
+
public async void PauseEncode()
{
await this.MakeHttpGetRequest("PauseEncode");
@@ -74,13 +78,13 @@ namespace HandBrakeWPF.Instance
this.MonitorEncodeProgress();
}
- public async void StartEncode(JsonEncodeObject jobToStart)
+ public void StartEncode(JsonEncodeObject jobToStart)
{
InitCommand initCommand = new InitCommand
{
EnableDiskLogging = false,
AllowDisconnectedWorker = false,
- DisableLibDvdNav = this.userSettingService.GetUserSetting<bool>(UserSettingConstants.DisableLibDvdNav),
+ DisableLibDvdNav = !this.userSettingService.GetUserSetting<bool>(UserSettingConstants.DisableLibDvdNav),
EnableHardwareAcceleration = true,
LogDirectory = DirectoryUtilities.GetLogDirectory(),
LogVerbosity = this.userSettingService.GetUserSetting<int>(UserSettingConstants.Verbosity)
@@ -91,7 +95,8 @@ namespace HandBrakeWPF.Instance
JsonSerializerSettings settings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore };
string job = JsonConvert.SerializeObject(new EncodeCommand { InitialiseCommand = initCommand, EncodeJob = jobToStart }, Formatting.None, settings);
- await this.MakeHttpJsonPostRequest("StartEncode", job);
+ var task = Task.Run(async () => await this.MakeHttpJsonPostRequest("StartEncode", job));
+ task.Wait();
this.MonitorEncodeProgress();
}
@@ -119,24 +124,14 @@ namespace HandBrakeWPF.Instance
public void Initialize(int verbosityLvl, bool noHardwareMode)
{
- this.StartServer();
- }
-
- public void Dispose()
- {
- this.workerProcess?.Dispose();
- }
-
- private async void StartServer()
- {
if (this.workerProcess == null || this.workerProcess.HasExited)
{
var plainTextBytes = Encoding.UTF8.GetBytes(Guid.NewGuid().ToString());
this.base64Token = Convert.ToBase64String(plainTextBytes);
workerProcess = new Process
- {
- StartInfo =
+ {
+ StartInfo =
{
FileName = "HandBrake.Worker.exe",
Arguments = string.Format(" --port={0} --token={1}", port, this.base64Token),
@@ -145,20 +140,44 @@ namespace HandBrakeWPF.Instance
RedirectStandardError = true,
CreateNoWindow = true
}
- };
+ };
workerProcess.Exited += this.WorkerProcess_Exited;
workerProcess.OutputDataReceived += this.WorkerProcess_OutputDataReceived;
workerProcess.ErrorDataReceived += this.WorkerProcess_OutputDataReceived;
-
+
workerProcess.Start();
workerProcess.BeginOutputReadLine();
workerProcess.BeginErrorReadLine();
+ // Set Process Priority
+ switch ((ProcessPriority)this.userSettingService.GetUserSetting<int>(UserSettingConstants.ProcessPriorityInt))
+ {
+ case ProcessPriority.High:
+ workerProcess.PriorityClass = ProcessPriorityClass.High;
+ break;
+ case ProcessPriority.AboveNormal:
+ workerProcess.PriorityClass = ProcessPriorityClass.AboveNormal;
+ break;
+ case ProcessPriority.Normal:
+ workerProcess.PriorityClass = ProcessPriorityClass.Normal;
+ break;
+ case ProcessPriority.Low:
+ workerProcess.PriorityClass = ProcessPriorityClass.Idle;
+ break;
+ default:
+ workerProcess.PriorityClass = ProcessPriorityClass.BelowNormal;
+ break;
+ }
this.logService.LogMessage(string.Format("Worker Process started with Process ID: {0} and port: {1}", this.workerProcess.Id, port));
}
}
+ public void Dispose()
+ {
+ this.workerProcess?.Dispose();
+ }
+
private void WorkerProcess_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
this.logService.LogMessage(e.Data);
@@ -213,7 +232,10 @@ namespace HandBrakeWPF.Instance
this.encodePollTimer?.Stop();
- this.workerProcess?.Kill();
+ if (this.workerProcess != null && !this.workerProcess.HasExited)
+ {
+ this.workerProcess?.Kill();
+ }
return;
}
@@ -259,7 +281,7 @@ namespace HandBrakeWPF.Instance
else if (taskState != null && taskState == TaskState.WorkDone)
{
this.encodePollTimer.Stop();
- if (!this.workerProcess.HasExited)
+ if (this.workerProcess != null && !this.workerProcess.HasExited)
{
this.workerProcess?.Kill();
}
diff --git a/win/CS/HandBrakeWPF/Properties/Resources.Designer.cs b/win/CS/HandBrakeWPF/Properties/Resources.Designer.cs
index 00d0044cd..876e5f43a 100644
--- a/win/CS/HandBrakeWPF/Properties/Resources.Designer.cs
+++ b/win/CS/HandBrakeWPF/Properties/Resources.Designer.cs
@@ -4916,15 +4916,6 @@ namespace HandBrakeWPF.Properties {
}
/// <summary>
- /// Looks up a localized string similar to The Queue has been paused. The currently running job will run to completion and no further jobs will start..
- /// </summary>
- public static string QueueViewModel_QueuePauseNotice {
- get {
- return ResourceManager.GetString("QueueViewModel_QueuePauseNotice", resourceCulture);
- }
- }
-
- /// <summary>
/// Looks up a localized string similar to Queue Paused.
/// </summary>
public static string QueueViewModel_QueuePending {
@@ -5250,6 +5241,15 @@ namespace HandBrakeWPF.Properties {
}
/// <summary>
+ /// Looks up a localized string similar to Cancel.
+ /// </summary>
+ public static string StaticPreviewView_CancelPreview {
+ get {
+ return ResourceManager.GetString("StaticPreviewView_CancelPreview", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to Duration:.
/// </summary>
public static string StaticPreviewView_Duration {
diff --git a/win/CS/HandBrakeWPF/Properties/Resources.de.resx b/win/CS/HandBrakeWPF/Properties/Resources.de.resx
index 69c9f01ff..a3f22724c 100644
--- a/win/CS/HandBrakeWPF/Properties/Resources.de.resx
+++ b/win/CS/HandBrakeWPF/Properties/Resources.de.resx
@@ -501,9 +501,6 @@ Bitte die Webseite auf Versionshinweise prüfen.</value>
<data name="QueueViewModel_QueuePending" xml:space="preserve">
<value>Warteschlange angehalten</value>
</data>
- <data name="QueueViewModel_QueuePauseNotice" xml:space="preserve">
- <value>Die Warteschlange wurde angehalten. Die aktuelle Aufgabe wird noch fertig gestellt und danach keine weiteren Aufgaben gestartet.</value>
- </data>
<data name="QueueViewModel_DelSelectedJobConfirmation" xml:space="preserve">
<value>Sollen die ausgewählten Aufgaben wirklich gelöscht werden?</value>
</data>
diff --git a/win/CS/HandBrakeWPF/Properties/Resources.es.resx b/win/CS/HandBrakeWPF/Properties/Resources.es.resx
index 8ad365676..eb0befdef 100644
--- a/win/CS/HandBrakeWPF/Properties/Resources.es.resx
+++ b/win/CS/HandBrakeWPF/Properties/Resources.es.resx
@@ -497,9 +497,6 @@ Puede encontrar más información en el registro de actividad.</value>
<data name="QueueViewModel_QueuePending" xml:space="preserve">
<value>Cola pausada</value>
</data>
- <data name="QueueViewModel_QueuePauseNotice" xml:space="preserve">
- <value>La cola ha sido pausada. El trabajo activo terminará pero no se iniciaran nuevos.</value>
- </data>
<data name="QueueViewModel_DelSelectedJobConfirmation" xml:space="preserve">
<value>¿Está seguro que desea borrar los trabajos seleccionados?</value>
</data>
diff --git a/win/CS/HandBrakeWPF/Properties/Resources.fr.resx b/win/CS/HandBrakeWPF/Properties/Resources.fr.resx
index 74d1af845..da93b5d98 100644
--- a/win/CS/HandBrakeWPF/Properties/Resources.fr.resx
+++ b/win/CS/HandBrakeWPF/Properties/Resources.fr.resx
@@ -498,9 +498,6 @@ Veuillez consulter le site Web pour les notes de version.</value>
<data name="QueueViewModel_QueuePending" xml:space="preserve">
<value>File mise en pause</value>
</data>
- <data name="QueueViewModel_QueuePauseNotice" xml:space="preserve">
- <value>La file a été mise en pause. Le travail en cours sera exécuté en totalité et aucun autre travail ne commencera.</value>
- </data>
<data name="QueueViewModel_DelSelectedJobConfirmation" xml:space="preserve">
<value>Êtes-vous sûr de vouloir supprimer les travaux sélectionnés ?</value>
</data>
diff --git a/win/CS/HandBrakeWPF/Properties/Resources.ja.resx b/win/CS/HandBrakeWPF/Properties/Resources.ja.resx
index d42b49f19..9566dbc84 100644
--- a/win/CS/HandBrakeWPF/Properties/Resources.ja.resx
+++ b/win/CS/HandBrakeWPF/Properties/Resources.ja.resx
@@ -497,9 +497,6 @@ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 021
<data name="QueueViewModel_QueuePending" xml:space="preserve">
<value>キュー一時停止</value>
</data>
- <data name="QueueViewModel_QueuePauseNotice" xml:space="preserve">
- <value>キューが一時停止されました。現在のジョブは完了まで実行され、それ以降のジョブは開始しません。</value>
- </data>
<data name="QueueViewModel_DelSelectedJobConfirmation" xml:space="preserve">
<value>選択したジョブを本当に削除しますか?</value>
</data>
diff --git a/win/CS/HandBrakeWPF/Properties/Resources.ko.resx b/win/CS/HandBrakeWPF/Properties/Resources.ko.resx
index 977dbcb45..5704eeebd 100644
--- a/win/CS/HandBrakeWPF/Properties/Resources.ko.resx
+++ b/win/CS/HandBrakeWPF/Properties/Resources.ko.resx
@@ -497,9 +497,6 @@ Activity log에 추가 정보가 있습니다.</value>
<data name="QueueViewModel_QueuePending" xml:space="preserve">
<value>대기열 일시 중지</value>
</data>
- <data name="QueueViewModel_QueuePauseNotice" xml:space="preserve">
- <value>대기열이 일시 중지되었습니다. 현재 실행 중인 작업은 완료될 때까지 실행되며 추가 작업은 실행되지 않습니다.</value>
- </data>
<data name="QueueViewModel_DelSelectedJobConfirmation" xml:space="preserve">
<value>선택된 작업을 삭제하시겠습니까?</value>
</data>
diff --git a/win/CS/HandBrakeWPF/Properties/Resources.resx b/win/CS/HandBrakeWPF/Properties/Resources.resx
index 7ba7be44a..de06cc56c 100644
--- a/win/CS/HandBrakeWPF/Properties/Resources.resx
+++ b/win/CS/HandBrakeWPF/Properties/Resources.resx
@@ -498,9 +498,6 @@ The Activity log may have further information.</value>
<data name="QueueViewModel_QueuePending" xml:space="preserve">
<value>Queue Paused</value>
</data>
- <data name="QueueViewModel_QueuePauseNotice" xml:space="preserve">
- <value>The Queue has been paused. The currently running job will run to completion and no further jobs will start.</value>
- </data>
<data name="QueueViewModel_DelSelectedJobConfirmation" xml:space="preserve">
<value>Are you sure you want to delete the selected jobs?</value>
</data>
@@ -2229,4 +2226,7 @@ Please choose a different preset.</value>
<data name="OptionsView_SimultaneousEncodes" xml:space="preserve">
<value>Number of simultaneous encodes:</value>
</data>
+ <data name="StaticPreviewView_CancelPreview" xml:space="preserve">
+ <value>Cancel</value>
+ </data>
</root> \ No newline at end of file
diff --git a/win/CS/HandBrakeWPF/Properties/Resources.ru.resx b/win/CS/HandBrakeWPF/Properties/Resources.ru.resx
index eaa4d4317..f1f709888 100644
--- a/win/CS/HandBrakeWPF/Properties/Resources.ru.resx
+++ b/win/CS/HandBrakeWPF/Properties/Resources.ru.resx
@@ -500,9 +500,6 @@ Foreign Audio Preferred, else First - Если существует дорожк
<data name="QueueViewModel_QueuePending" xml:space="preserve">
<value>Очередь приостановлена</value>
</data>
- <data name="QueueViewModel_QueuePauseNotice" xml:space="preserve">
- <value>Очередь была приостановлена. Текущее задание будет выполнено, а будущие - не начнутся.</value>
- </data>
<data name="QueueViewModel_DelSelectedJobConfirmation" xml:space="preserve">
<value>Вы уверены, что хотите удалить выбранные задания?</value>
</data>
diff --git a/win/CS/HandBrakeWPF/Properties/Resources.tr.resx b/win/CS/HandBrakeWPF/Properties/Resources.tr.resx
index 42757b74b..0102da37f 100644
--- a/win/CS/HandBrakeWPF/Properties/Resources.tr.resx
+++ b/win/CS/HandBrakeWPF/Properties/Resources.tr.resx
@@ -500,9 +500,6 @@ Faaliyet günlüğü daha fazla bilgi içerebilir.</value>
<data name="QueueViewModel_QueuePending" xml:space="preserve">
<value>Sıra Duraklatıldı</value>
</data>
- <data name="QueueViewModel_QueuePauseNotice" xml:space="preserve">
- <value>Sıra duraklatıldı. Halen çalışmakta olan iş tamamlanmaya devam eder ve başka iş başlatılmaz.</value>
- </data>
<data name="QueueViewModel_DelSelectedJobConfirmation" xml:space="preserve">
<value>Seçilen işleri silmek istediğinizden emin misiniz?</value>
</data>
diff --git a/win/CS/HandBrakeWPF/Properties/Resources.zh.resx b/win/CS/HandBrakeWPF/Properties/Resources.zh.resx
index 189616dbe..213ff6e0d 100644
--- a/win/CS/HandBrakeWPF/Properties/Resources.zh.resx
+++ b/win/CS/HandBrakeWPF/Properties/Resources.zh.resx
@@ -493,9 +493,6 @@ Foreign Audio Preferred, else First - 如果存在外语轨道,则会将其烧
<data name="QueueViewModel_QueuePending" xml:space="preserve">
<value>列队已暂停</value>
</data>
- <data name="QueueViewModel_QueuePauseNotice" xml:space="preserve">
- <value>队列已暂停。当前正在运行的作业将运行完成,不会再启动其他作业。</value>
- </data>
<data name="QueueViewModel_DelSelectedJobConfirmation" xml:space="preserve">
<value>您确定要删除所选作业吗?</value>
</data>
diff --git a/win/CS/HandBrakeWPF/Services/Encode/LibEncode.cs b/win/CS/HandBrakeWPF/Services/Encode/LibEncode.cs
index e6d8cb7c0..fdd2a01e5 100644
--- a/win/CS/HandBrakeWPF/Services/Encode/LibEncode.cs
+++ b/win/CS/HandBrakeWPF/Services/Encode/LibEncode.cs
@@ -40,6 +40,7 @@ namespace HandBrakeWPF.Services.Encode
private readonly IUserSettingService userSettingService;
private readonly ILogInstanceManager logInstanceManager;
private readonly IHbFunctionsProvider hbFunctionsProvider;
+ private readonly object portLock = new object();
private IEncodeInstance instance;
private DateTime startTime;
private EncodeTask currentTask;
@@ -107,19 +108,24 @@ namespace HandBrakeWPF.Services.Encode
}
int verbosity = this.userSettingService.GetUserSetting<int>(UserSettingConstants.Verbosity);
- this.instance = task.IsPreviewEncode ? HandBrakeInstanceManager.GetPreviewInstance(verbosity, this.userSettingService) : HandBrakeInstanceManager.GetEncodeInstance(verbosity, configuration, this.encodeLogService, userSettingService);
-
- this.instance.EncodeCompleted += this.InstanceEncodeCompleted;
- this.instance.EncodeProgress += this.InstanceEncodeProgress;
- this.IsEncoding = true;
- this.isPreviewInstance = task.IsPreviewEncode;
+ // Prevent port stealing if multiple jobs start at the same time.
+ lock (portLock)
+ {
+ this.instance = task.IsPreviewEncode ? HandBrakeInstanceManager.GetPreviewInstance(verbosity, this.userSettingService) : HandBrakeInstanceManager.GetEncodeInstance(verbosity, configuration, this.encodeLogService, userSettingService);
+
+ this.instance.EncodeCompleted += this.InstanceEncodeCompleted;
+ this.instance.EncodeProgress += this.InstanceEncodeProgress;
- // Verify the Destination Path Exists, and if not, create it.
- this.VerifyEncodeDestinationPath(task);
+ this.IsEncoding = true;
+ this.isPreviewInstance = task.IsPreviewEncode;
- // Get an EncodeJob object for the Interop Library
- this.instance.StartEncode(EncodeTaskFactory.Create(task, configuration, hbFunctionsProvider.GetHbFunctionsWrapper()));
+ // Verify the Destination Path Exists, and if not, create it.
+ this.VerifyEncodeDestinationPath(task);
+
+ // Get an EncodeJob object for the Interop Library
+ this.instance.StartEncode(EncodeTaskFactory.Create(task, configuration, hbFunctionsProvider.GetHbFunctionsWrapper()));
+ }
// Fire the Encode Started Event
this.InvokeEncodeStarted(System.EventArgs.Empty);
diff --git a/win/CS/HandBrakeWPF/Services/Logging/LogInstanceManager.cs b/win/CS/HandBrakeWPF/Services/Logging/LogInstanceManager.cs
index 4e1290024..16c9aa7dc 100644
--- a/win/CS/HandBrakeWPF/Services/Logging/LogInstanceManager.cs
+++ b/win/CS/HandBrakeWPF/Services/Logging/LogInstanceManager.cs
@@ -19,13 +19,26 @@ namespace HandBrakeWPF.Services.Logging
public class LogInstanceManager : ILogInstanceManager
{
- private Dictionary<string, ILog> logInstances = new Dictionary<string, ILog>();
+ private readonly IUserSettingService userSettingService;
+ private readonly object instanceLock = new object();
+ private Dictionary<string, ILog> logInstances = new Dictionary<string, ILog>();
+
private int maxInstances;
public LogInstanceManager(IUserSettingService userSettingService)
{
- this.maxInstances = userSettingService.GetUserSetting<int>(UserSettingConstants.SimultaneousEncodes);
+ this.userSettingService = userSettingService;
+ this.maxInstances = this.userSettingService.GetUserSetting<int>(UserSettingConstants.SimultaneousEncodes);
+ userSettingService.SettingChanged += this.UserSettingService_SettingChanged;
+ }
+
+ private void UserSettingService_SettingChanged(object sender, HandBrakeWPF.EventArgs.SettingChangedEventArgs e)
+ {
+ if (e.Key == UserSettingConstants.SimultaneousEncodes)
+ {
+ this.maxInstances = this.userSettingService.GetUserSetting<int>(UserSettingConstants.SimultaneousEncodes);
+ }
}
public event EventHandler NewLogInstanceRegistered;
@@ -36,19 +49,22 @@ namespace HandBrakeWPF.Services.Logging
public void RegisterLoggerInstance(string filename, ILog log, bool isMaster)
{
- if (string.IsNullOrEmpty(this.ApplicationAndScanLog))
+ lock (this.instanceLock)
{
- // The application startup sets the initial log file.
- this.ApplicationAndScanLog = filename;
- }
+ if (string.IsNullOrEmpty(this.ApplicationAndScanLog))
+ {
+ // The application startup sets the initial log file.
+ this.ApplicationAndScanLog = filename;
+ }
- this.logInstances.Add(filename, log);
+ this.logInstances.Add(filename, log);
- this.CleanupInstance();
+ this.CleanupInstance();
- if (isMaster)
- {
- this.MasterLogInstance = log;
+ if (isMaster)
+ {
+ this.MasterLogInstance = log;
+ }
}
this.OnNewLogInstanceRegistered();
@@ -56,20 +72,26 @@ namespace HandBrakeWPF.Services.Logging
public List<string> GetLogFiles()
{
- return this.logInstances.Keys.ToList();
+ lock (this.instanceLock)
+ {
+ return this.logInstances.Keys.ToList();
+ }
}
public ILog GetLogInstance(string filename)
{
- if (string.IsNullOrEmpty(filename))
+ lock (this.instanceLock)
{
- return null;
- }
-
- ILog logger;
- if (this.logInstances.TryGetValue(filename, out logger))
- {
- return logger;
+ if (string.IsNullOrEmpty(filename))
+ {
+ return null;
+ }
+
+ ILog logger;
+ if (this.logInstances.TryGetValue(filename, out logger))
+ {
+ return logger;
+ }
}
return null;
@@ -87,7 +109,7 @@ namespace HandBrakeWPF.Services.Logging
if (encodeLogs.Count > this.maxInstances)
{
this.logInstances.Remove(removalKey);
- }
+ }
}
}
}
diff --git a/win/CS/HandBrakeWPF/Services/Queue/Interfaces/IQueueService.cs b/win/CS/HandBrakeWPF/Services/Queue/Interfaces/IQueueService.cs
index 1a2a04cce..5bf759917 100644
--- a/win/CS/HandBrakeWPF/Services/Queue/Interfaces/IQueueService.cs
+++ b/win/CS/HandBrakeWPF/Services/Queue/Interfaces/IQueueService.cs
@@ -63,6 +63,11 @@ namespace HandBrakeWPF.Services.Queue.Interfaces
int ErrorCount { get; }
/// <summary>
+ /// Gets the number of completed jobs.
+ /// </summary>
+ int CompletedCount { get; }
+
+ /// <summary>
/// Gets a value indicating whether IsProcessing.
/// </summary>
bool IsProcessing { get; }
diff --git a/win/CS/HandBrakeWPF/Services/Queue/QueueService.cs b/win/CS/HandBrakeWPF/Services/Queue/QueueService.cs
index 6cbf709e5..41cba661f 100644
--- a/win/CS/HandBrakeWPF/Services/Queue/QueueService.cs
+++ b/win/CS/HandBrakeWPF/Services/Queue/QueueService.cs
@@ -59,9 +59,12 @@ namespace HandBrakeWPF.Services.Queue
private readonly ObservableCollection<QueueTask> queue = new ObservableCollection<QueueTask>();
private readonly string queueFile;
+ private readonly object queueFileLock = new object();
+
private bool clearCompleted;
private int allowedInstances;
private int jobIdCounter = 0;
+ private bool processIsolationEnabled;
public QueueService(IUserSettingService userSettingService, ILog logService, IErrorService errorService, ILogInstanceManager logInstanceManager, IHbFunctionsProvider hbFunctionsProvider)
{
@@ -75,6 +78,7 @@ namespace HandBrakeWPF.Services.Queue
this.queueFile = string.Format("{0}{1}.json", QueueRecoveryHelper.QueueFileName, GeneralUtilities.ProcessId);
this.allowedInstances = this.userSettingService.GetUserSetting<int>(UserSettingConstants.SimultaneousEncodes);
+ this.processIsolationEnabled = this.userSettingService.GetUserSetting<bool>(UserSettingConstants.ProcessIsolationEnabled);
}
public event EventHandler<QueueProgressEventArgs> JobProcessingStarted;
@@ -105,6 +109,8 @@ namespace HandBrakeWPF.Services.Queue
}
}
+ public int CompletedCount => this.queue.Count(item => item.Status == QueueItemStatus.Completed);
+
public bool IsPaused { get; private set; }
public bool IsProcessing { get; private set; }
@@ -130,33 +136,31 @@ namespace HandBrakeWPF.Services.Queue
public void BackupQueue(string exportPath)
{
- Stopwatch watch = Stopwatch.StartNew();
-
- string appDataPath = DirectoryUtilities.GetUserStoragePath(VersionHelper.IsNightly());
- string tempPath = !string.IsNullOrEmpty(exportPath)
- ? exportPath
- : Path.Combine(appDataPath, string.Format(this.queueFile, string.Empty));
-
- // Make a copy of the file before we replace it. This way, if we crash we can recover.
- if (File.Exists(tempPath))
+ lock (this.queueFileLock)
{
- File.Copy(tempPath, tempPath + ".last");
- }
+ string appDataPath = DirectoryUtilities.GetUserStoragePath(VersionHelper.IsNightly());
+ string tempPath = !string.IsNullOrEmpty(exportPath)
+ ? exportPath
+ : Path.Combine(appDataPath, string.Format(this.queueFile, string.Empty));
- using (StreamWriter writer = new StreamWriter(tempPath))
- {
- List<QueueTask> tasks = this.queue.Where(item => item.Status != QueueItemStatus.Completed).ToList();
- string queueJson = JsonConvert.SerializeObject(tasks, Formatting.Indented);
- writer.Write(queueJson);
- }
+ // Make a copy of the file before we replace it. This way, if we crash we can recover.
+ if (File.Exists(tempPath))
+ {
+ File.Copy(tempPath, tempPath + ".last");
+ }
- if (File.Exists(tempPath + ".last"))
- {
- File.Delete(tempPath + ".last");
- }
+ using (StreamWriter writer = new StreamWriter(tempPath))
+ {
+ List<QueueTask> tasks = this.queue.Where(item => item.Status != QueueItemStatus.Completed).ToList();
+ string queueJson = JsonConvert.SerializeObject(tasks, Formatting.Indented);
+ writer.Write(queueJson);
+ }
- watch.Stop();
- Debug.WriteLine("Queue Save (ms): " + watch.ElapsedMilliseconds);
+ if (File.Exists(tempPath + ".last"))
+ {
+ File.Delete(tempPath + ".last");
+ }
+ }
}
public void ExportCliJson(string exportPath)
@@ -447,6 +451,9 @@ namespace HandBrakeWPF.Services.Queue
this.IsPaused = false;
this.clearCompleted = isClearCompleted;
+ this.allowedInstances = this.userSettingService.GetUserSetting<int>(UserSettingConstants.SimultaneousEncodes);
+ this.processIsolationEnabled = this.userSettingService.GetUserSetting<bool>(UserSettingConstants.ProcessIsolationEnabled);
+
// Unpause all active jobs.
foreach (ActiveJob job in this.activeJobs)
{
@@ -523,6 +530,11 @@ namespace HandBrakeWPF.Services.Queue
private void ProcessNextJob()
{
+ if (!this.processIsolationEnabled)
+ {
+ this.allowedInstances = 1;
+ }
+
if (this.activeJobs.Count >= this.allowedInstances)
{
return;
@@ -555,8 +567,11 @@ namespace HandBrakeWPF.Services.Queue
{
this.BackupQueue(string.Empty);
- // Fire the event to tell connected services.
- this.InvokeQueueCompleted(new QueueCompletedEventArgs(false));
+ if (!this.activeJobs.Any(a => a.IsEncoding))
+ {
+ // Fire the event to tell connected services.
+ this.InvokeQueueCompleted(new QueueCompletedEventArgs(false));
+ }
}
}
diff --git a/win/CS/HandBrakeWPF/ViewModels/MainViewModel.cs b/win/CS/HandBrakeWPF/ViewModels/MainViewModel.cs
index f88adaded..c1124817e 100644
--- a/win/CS/HandBrakeWPF/ViewModels/MainViewModel.cs
+++ b/win/CS/HandBrakeWPF/ViewModels/MainViewModel.cs
@@ -62,8 +62,6 @@ namespace HandBrakeWPF.ViewModels
/// </summary>
public class MainViewModel : ViewModelBase, IMainViewModel
{
- #region Private Variables and Services
-
private readonly IQueueService queueProcessor;
private readonly IPresetService presetService;
private readonly IErrorService errorService;
@@ -96,13 +94,6 @@ namespace HandBrakeWPF.ViewModels
private bool isModifiedPreset;
private bool updateAvailable;
- #endregion
-
- /// <summary>
- /// Initializes a new instance of the <see cref="MainViewModel"/> class.
- /// The viewmodel for HandBrakes main window.
- /// </summary>
- /// <remarks>whenDoneService must be a serivce here!</remarks>
public MainViewModel(
IUserSettingService userSettingService,
IScan scanService,
@@ -1070,6 +1061,8 @@ namespace HandBrakeWPF.ViewModels
}
}
+ public bool IsMultiProcess { get; set; }
+
#endregion
#region Commands
@@ -2495,6 +2488,7 @@ namespace HandBrakeWPF.ViewModels
private void QueueCompleted(object sender, EventArgs e)
{
this.NotifyOfPropertyChange(() => this.IsEncoding);
+ this.NotifyOfPropertyChange(() => this.StartLabel);
Execute.OnUIThread(
() =>
@@ -2530,7 +2524,11 @@ namespace HandBrakeWPF.ViewModels
Execute.OnUIThread(
() =>
{
- this.ProgramStatusLabel = string.Format(Resources.Main_XEncodesPending, this.queueProcessor.Count);
+ if (!this.queueProcessor.IsEncoding)
+ {
+ this.ProgramStatusLabel = string.Format(Resources.Main_XEncodesPending, this.queueProcessor.Count);
+ }
+
this.NotifyOfPropertyChange(() => this.QueueLabel);
this.NotifyOfPropertyChange(() => this.StartLabel);
this.NotifyOfPropertyChange(() => this.IsEncoding);
@@ -2545,15 +2543,20 @@ namespace HandBrakeWPF.ViewModels
this.ProgramStatusLabel = Resources.Main_QueuePaused;
this.NotifyOfPropertyChange(() => this.QueueLabel);
this.NotifyOfPropertyChange(() => this.StartLabel);
+ this.NotifyOfPropertyChange(() => this.IsEncoding);
});
}
-
-
+
private void QueueProcessor_QueueJobStatusChanged(object sender, EventArgs e)
{
List<QueueProgressStatus> queueJobStatuses = this.queueProcessor.GetQueueProgressStatus();
string jobsPending = string.Format(Resources.Main_JobsPending_addon, this.queueProcessor.Count);
+ if (this.queueProcessor.IsPaused)
+ {
+ return;
+ }
+
Execute.OnUIThread(
() =>
{
@@ -2579,10 +2582,15 @@ namespace HandBrakeWPF.ViewModels
this.WindowTitle = string.Format(Resources.WindowTitleStatus, Resources.HandBrake_Title, this.ProgressPercentage, status.Task, status.TaskCount);
this.notifyIconService.SetTooltip(string.Format(Resources.TaskTrayStatusTitle, Resources.HandBrake_Title, this.ProgressPercentage, status.Task, status.TaskCount, status.EstimatedTimeLeft));
}
+
+ this.IsMultiProcess = false;
+ this.NotifyOfPropertyChange(() => this.IsMultiProcess);
}
else if (queueJobStatuses.Count > 1)
{
- this.ProgramStatusLabel = "Multiple Jobs Running."; // TODO Implement later.
+ this.ProgramStatusLabel = string.Format("{0} jobs completed. {1}Working on {2} jobs with {3} waiting to be processed.", this.queueProcessor.CompletedCount, Environment.NewLine, queueJobStatuses.Count, this.queueProcessor.Count);
+ this.IsMultiProcess = true;
+ this.NotifyOfPropertyChange(() => this.IsMultiProcess);
}
else
{
@@ -2591,6 +2599,9 @@ namespace HandBrakeWPF.ViewModels
this.WindowTitle = Resources.HandBrake_Title;
this.notifyIconService.SetTooltip(this.WindowTitle);
+ this.IsMultiProcess = false;
+ this.NotifyOfPropertyChange(() => this.IsMultiProcess);
+
if (this.windowsSeven.IsWindowsSeven)
{
this.windowsSeven.SetTaskBarProgressToNoProgress();
diff --git a/win/CS/HandBrakeWPF/ViewModels/QueueViewModel.cs b/win/CS/HandBrakeWPF/ViewModels/QueueViewModel.cs
index 1d3650264..2926e40e5 100644
--- a/win/CS/HandBrakeWPF/ViewModels/QueueViewModel.cs
+++ b/win/CS/HandBrakeWPF/ViewModels/QueueViewModel.cs
@@ -231,9 +231,6 @@ namespace HandBrakeWPF.ViewModels
this.JobsPending = string.Format(Resources.QueueViewModel_JobsPending, this.queueProcessor.Count);
this.IsQueueRunning = false;
-
- this.errorService.ShowMessageBox(Resources.QueueViewModel_QueuePauseNotice, Resources.QueueViewModel_Queue,
- MessageBoxButton.OK, MessageBoxImage.Information);
}
public void PauseQueueToolbar()
diff --git a/win/CS/HandBrakeWPF/ViewModels/StaticPreviewViewModel.cs b/win/CS/HandBrakeWPF/ViewModels/StaticPreviewViewModel.cs
index 996d93949..52d4bb4b3 100644
--- a/win/CS/HandBrakeWPF/ViewModels/StaticPreviewViewModel.cs
+++ b/win/CS/HandBrakeWPF/ViewModels/StaticPreviewViewModel.cs
@@ -41,9 +41,6 @@ namespace HandBrakeWPF.ViewModels
using OutputFormat = HandBrakeWPF.Services.Encode.Model.Models.OutputFormat;
using PointToPointMode = HandBrakeWPF.Services.Encode.Model.Models.PointToPointMode;
- /// <summary>
- /// The Static Preview View Model
- /// </summary>
public class StaticPreviewViewModel : ViewModelBase, IStaticPreviewViewModel
{
private readonly IScan scanService;
@@ -61,8 +58,6 @@ namespace HandBrakeWPF.ViewModels
private bool useSystemDefaultPlayer;
private bool previewRotateFlip;
- #region Constructors and Destructors
-
public StaticPreviewViewModel(IScan scanService, IUserSettingService userSettingService, IErrorService errorService, IHbFunctionsProvider hbFunctionsProvider, ILog logService, ILogInstanceManager logInstanceManager)
{
this.scanService = scanService;
@@ -86,11 +81,7 @@ namespace HandBrakeWPF.ViewModels
this.previewRotateFlip = userSettingService.GetUserSetting<bool>(UserSettingConstants.PreviewRotationFlip);
this.NotifyOfPropertyChange(() => this.previewRotateFlip); // Don't want to trigger an Update, so setting the backing variable.
}
-
- #endregion
-
- #region Public Properties
-
+
/// <summary>
/// Gets or sets the height.
/// </summary>
@@ -233,10 +224,6 @@ namespace HandBrakeWPF.ViewModels
}
}
- #endregion
-
- #region LivePreviewProperties
-
/// <summary>
/// Gets AvailableDurations.
/// </summary>
@@ -350,9 +337,8 @@ namespace HandBrakeWPF.ViewModels
/// Gets or sets a value indicating whether can play.
/// </summary>
public bool CanPlay { get; set; }
- #endregion
- #region Public Methods and Operators
+ public bool IsOpen { get; set; }
/// <summary>
/// The update preview frame.
@@ -372,11 +358,6 @@ namespace HandBrakeWPF.ViewModels
this.ScannedSource = scannedSource;
}
- /// <summary>
- /// Gets or sets a value indicating whether is open.
- /// </summary>
- public bool IsOpen { get; set; }
-
public void NextPreview()
{
int maxPreview = this.userSettingService.GetUserSetting<int>(UserSettingConstants.PreviewScanCount);
@@ -442,12 +423,6 @@ namespace HandBrakeWPF.ViewModels
}
}
- /// <summary>
- /// The preview size changed.
- /// </summary>
- /// <param name="ea">
- /// The ea.
- /// </param>
public int FixWidth(int width)
{
Rect workArea = SystemParameters.WorkArea;
@@ -470,12 +445,6 @@ namespace HandBrakeWPF.ViewModels
return height;
}
- #endregion
-
- #region Public Method - Live Preview
-
- #region Public Methods
-
/// <summary>
/// Close this window.
/// </summary>
@@ -500,19 +469,19 @@ namespace HandBrakeWPF.ViewModels
{
this.IsEncoding = true;
if (File.Exists(this.CurrentlyPlaying))
+ {
File.Delete(this.CurrentlyPlaying);
+ }
}
catch (Exception)
{
this.IsEncoding = false;
- this.errorService.ShowMessageBox(Resources.StaticPreview_UnableToDeletePreview,
- Resources.Error, MessageBoxButton.OK, MessageBoxImage.Error);
+ this.errorService.ShowMessageBox(Resources.StaticPreview_UnableToDeletePreview, Resources.Error, MessageBoxButton.OK, MessageBoxImage.Error);
}
if (this.Task == null || string.IsNullOrEmpty(Task.Source))
{
- this.errorService.ShowMessageBox(Resources.StaticPreviewViewModel_ScanFirst,
- Resources.Error, MessageBoxButton.OK, MessageBoxImage.Error);
+ this.errorService.ShowMessageBox(Resources.StaticPreviewViewModel_ScanFirst, Resources.Error, MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
@@ -540,6 +509,7 @@ namespace HandBrakeWPF.ViewModels
formatExtension = "mkv";
break;
}
+
string filename = Path.ChangeExtension(Path.GetTempFileName(), formatExtension);
encodeTask.Destination = filename;
this.CurrentlyPlaying = filename;
@@ -579,9 +549,14 @@ namespace HandBrakeWPF.ViewModels
ThreadPool.QueueUserWorkItem(this.CreatePreview, task);
}
- #endregion
+ public void CancelEncode()
+ {
+ if (this.encodeService.IsEncoding)
+ {
+ this.encodeService.Stop();
+ }
+ }
- #region Private Methods
/// <summary>
/// Play the Encoded file
@@ -665,10 +640,6 @@ namespace HandBrakeWPF.ViewModels
this.userSettingService.SetUserSetting(UserSettingConstants.LastPreviewDuration, this.Duration);
}
- #endregion
-
- #region Event Handlers
-
/// <summary>
/// Handle Encode Progress Events
/// </summary>
@@ -702,9 +673,10 @@ namespace HandBrakeWPF.ViewModels
this.encodeService.EncodeCompleted -= this.encodeService_EncodeCompleted;
this.encodeService.EncodeStatusChanged -= this.encodeService_EncodeStatusChanged;
- this.PlayFile();
+ if (e.ErrorInformation != "1")
+ {
+ this.PlayFile();
+ }
}
- #endregion
- #endregion
}
} \ No newline at end of file
diff --git a/win/CS/HandBrakeWPF/Views/MainView.xaml b/win/CS/HandBrakeWPF/Views/MainView.xaml
index 10712ac05..df77fc923 100644
--- a/win/CS/HandBrakeWPF/Views/MainView.xaml
+++ b/win/CS/HandBrakeWPF/Views/MainView.xaml
@@ -797,10 +797,10 @@
</Controls:AlertPanel>
<!-- Status Bar -->
- <StatusBar Name="statusBar1" Height="32" Grid.Row="2" Grid.ColumnSpan="2" HorizontalAlignment="Stretch" VerticalAlignment="Bottom">
+ <StatusBar Name="statusBar" Grid.Row="2" Grid.ColumnSpan="2" HorizontalAlignment="Stretch" VerticalAlignment="Bottom">
<StatusBarItem>
- <ProgressBar Value="{Binding ProgressPercentage}" Visibility="{Binding IsEncoding, Converter={StaticResource boolToVisConverter}}"
+ <ProgressBar Value="{Binding ProgressPercentage}" IsIndeterminate="{Binding IsMultiProcess}" Visibility="{Binding IsEncoding, Converter={StaticResource boolToVisConverter}}"
Width="100" Height="18" VerticalAlignment="Center" />
</StatusBarItem>
<StatusBarItem>
diff --git a/win/CS/HandBrakeWPF/Views/OptionsView.xaml b/win/CS/HandBrakeWPF/Views/OptionsView.xaml
index 67d86bdee..baaadb5e5 100644
--- a/win/CS/HandBrakeWPF/Views/OptionsView.xaml
+++ b/win/CS/HandBrakeWPF/Views/OptionsView.xaml
@@ -416,8 +416,7 @@
<StackPanel Orientation="Horizontal" Margin="10,10,0,0" >
<TextBlock Text="{x:Static Properties:Resources.OptionsView_SimultaneousEncodes}" VerticalAlignment="Center"/>
- <ComboBox ItemsSource="{Binding SimultaneousEncodesList}" SelectedItem="{Binding SimultaneousEncodes}" IsEnabled="false" /> <!--IsEnabled="{Binding RemoteServiceEnabled}"-->
- <TextBlock Text="(Coming Soon!)" Margin="10,0,0,0" FontWeight="Bold" VerticalAlignment="Center" />
+ <ComboBox ItemsSource="{Binding SimultaneousEncodesList}" SelectedItem="{Binding SimultaneousEncodes}" IsEnabled="{Binding RemoteServiceEnabled}" />
</StackPanel>
</StackPanel>
diff --git a/win/CS/HandBrakeWPF/Views/ShellView.xaml b/win/CS/HandBrakeWPF/Views/ShellView.xaml
index e7a6feba6..dfa3f6f24 100644
--- a/win/CS/HandBrakeWPF/Views/ShellView.xaml
+++ b/win/CS/HandBrakeWPF/Views/ShellView.xaml
@@ -7,9 +7,9 @@
xmlns:cal="http://www.caliburnproject.org"
Title="{Data:Binding Path=MainViewModel.WindowTitle}"
Width="1018"
- Height="650"
+ Height="660"
MinWidth="1018"
- MinHeight="650"
+ MinHeight="660"
AllowDrop="True"
SnapsToDevicePixels="True"
UseLayoutRounding="True"
diff --git a/win/CS/HandBrakeWPF/Views/StaticPreviewView.xaml b/win/CS/HandBrakeWPF/Views/StaticPreviewView.xaml
index db622c247..584270569 100644
--- a/win/CS/HandBrakeWPF/Views/StaticPreviewView.xaml
+++ b/win/CS/HandBrakeWPF/Views/StaticPreviewView.xaml
@@ -43,7 +43,9 @@
<CheckBox IsChecked="{Binding PreviewRotateFlip}" Content="{x:Static Properties:Resources.StaticPreviewView_PreviewRotationFlip}" Foreground="White" Grid.Row="1" Margin="0,0,0,10" />
<StackPanel Orientation="Horizontal" Grid.Row="2" HorizontalAlignment="Left">
- <Button Content="{x:Static Properties:Resources.StaticPreviewView_LivePreview}" Padding="8,2" cal:Message.Attach="[Event Click] = [Action Play]" />
+ <Button Content="{x:Static Properties:Resources.StaticPreviewView_LivePreview}" Padding="8,2" cal:Message.Attach="[Event Click] = [Action Play]" Visibility="{Binding IsEncoding, Converter={StaticResource booleanToVisibilityConverter}, ConverterParameter=true}" />
+ <Button Content="{x:Static Properties:Resources.StaticPreviewView_CancelPreview}" Padding="8,2" cal:Message.Attach="[Event Click] = [Action CancelEncode]" Visibility="{Binding IsEncoding, Converter={StaticResource booleanToVisibilityConverter}, ConverterParameter=false}" />
+
<TextBlock Margin="10,0,5,0" VerticalAlignment="Center" Foreground="White" Text="{x:Static Properties:Resources.StaticPreviewView_Duration}" />
<ComboBox Width="60"
ItemsSource="{Binding AvailableDurations}"