// -------------------------------------------------------------------------------------------------------------------- // // This file is part of the HandBrake source code - It may be used under the terms of the GNU General Public License. // // // The instant view model. // // -------------------------------------------------------------------------------------------------------------------- namespace HandBrakeWPF.ViewModels { using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Threading; using System.Windows; using Caliburn.Micro; using HandBrake.ApplicationServices; using HandBrake.ApplicationServices.EventArgs; using HandBrake.ApplicationServices.Model; using HandBrake.ApplicationServices.Services.Encode.EventArgs; using HandBrake.ApplicationServices.Services.Encode.Interfaces; using HandBrake.ApplicationServices.Services.Interfaces; using HandBrake.ApplicationServices.Services.Scan.EventArgs; using HandBrake.ApplicationServices.Services.Scan.Interfaces; using HandBrake.ApplicationServices.Services.Scan.Model; using HandBrake.ApplicationServices.Utilities; using HandBrakeWPF.Commands; using HandBrakeWPF.Factories; using HandBrakeWPF.Helpers; using HandBrakeWPF.Model; using HandBrakeWPF.Services.Interfaces; using HandBrakeWPF.Services.Presets.Interfaces; using HandBrakeWPF.Services.Presets.Model; using HandBrakeWPF.Utilities; using HandBrakeWPF.ViewModels.Interfaces; using HandBrakeWPF.Views; using Microsoft.Win32; using Ookii.Dialogs.Wpf; /// /// The instant view model. /// public class InstantViewModel : ViewModelBase, IInstantViewModel { #region Constants and Fields /// /// The encode service. /// private readonly IEncodeServiceWrapper encodeService; /// /// The error service. /// private readonly IErrorService errorService; /// /// The preset service. /// private readonly IPresetService presetService; /// /// The queue processor. /// private readonly IQueueProcessor queueProcessor; /// /// The scan service. /// private readonly IScan scanService; /// /// The shell view model. /// private readonly IShellViewModel shellViewModel; /// /// The update service. /// private readonly IUpdateService updateService; /// /// The user setting service. /// private readonly IUserSettingService userSettingService; /// /// Windows 7 API Pack wrapper /// private readonly Win7 windowsSeven = new Win7(); /// /// The is encoding. /// private bool isEncoding; /// /// The last percentage complete value. /// private int lastEncodePercentage; /// /// The ordered by duration. /// private bool orderedByDuration; /// /// The ordered by title. /// private bool orderedByTitle; /// /// The output directory. /// private string outputDirectory; /// /// The program status label. /// private string programStatusLabel; /// /// The scanned source. /// private Source scannedSource; /// /// The selected preset. /// private Preset selectedPreset; /// /// The show status window. /// private bool showStatusWindow; /// /// The source label. /// private string sourceLabel; /// /// The status label. /// private string statusLabel; #endregion #region Constructors and Destructors /// /// Initializes a new instance of the class. /// /// /// The user setting service. /// /// /// The scan service. /// /// /// The encode service. /// /// /// The preset service. /// /// /// The error service. /// /// /// The shell view model. /// /// /// The update service. /// /// /// The notification service. /// /// /// The when done service. /// public InstantViewModel( IUserSettingService userSettingService, IScan scanService, IEncodeServiceWrapper encodeService, IPresetService presetService, IErrorService errorService, IShellViewModel shellViewModel, IUpdateService updateService, INotificationService notificationService, IPrePostActionService whenDoneService) { this.userSettingService = userSettingService; this.scanService = scanService; this.encodeService = encodeService; this.presetService = presetService; this.errorService = errorService; this.shellViewModel = shellViewModel; this.updateService = updateService; this.queueProcessor = IoC.Get(); // Setup Properties this.TitleList = new BindingList(); this.ScannedSource = new Source(); // Setup Events this.scanService.ScanStared += this.ScanStared; this.scanService.ScanCompleted += this.ScanCompleted; this.scanService.ScanStatusChanged += this.ScanStatusChanged; this.queueProcessor.JobProcessingStarted += this.QueueProcessorJobProcessingStarted; this.queueProcessor.QueueCompleted += this.QueueCompleted; this.queueProcessor.QueueChanged += this.QueueChanged; this.queueProcessor.EncodeService.EncodeStatusChanged += this.EncodeStatusChanged; this.Presets = this.presetService.Presets; this.CancelScanCommand = new CancelScanCommand(this.scanService); } #endregion #region Properties /// /// Gets or sets the cancel scan command. /// public CancelScanCommand CancelScanCommand { get; set; } /// /// Gets or sets a value indicating whether IsEncoding. /// public bool IsEncoding { get { return this.isEncoding; } set { this.isEncoding = value; this.NotifyOfPropertyChange(() => this.IsEncoding); } } /// /// Gets or sets a value indicating whether ordered by duration. /// public bool OrderedByDuration { get { return this.orderedByDuration; } set { this.orderedByDuration = value; this.NotifyOfPropertyChange(() => this.OrderedByDuration); } } /// /// Gets or sets a value indicating whether ordered by title. /// public bool OrderedByTitle { get { return this.orderedByTitle; } set { this.orderedByTitle = value; this.NotifyOfPropertyChange(() => this.OrderedByTitle); } } /// /// Gets or sets the output directory. /// public string OutputDirectory { get { return this.outputDirectory; } set { this.outputDirectory = value; this.NotifyOfPropertyChange(() => this.OutputDirectory); } } /// /// Gets or sets Presets. /// public IEnumerable Presets { get; set; } /// /// Gets or sets the Program Status Toolbar Label /// This indicates the status of HandBrake /// public string ProgramStatusLabel { get { return string.IsNullOrEmpty(this.programStatusLabel) ? "Ready" : this.programStatusLabel; } set { if (!Equals(this.programStatusLabel, value)) { this.programStatusLabel = value; this.NotifyOfPropertyChange(() => this.ProgramStatusLabel); } } } /// /// Gets or sets a value indicating progress percentage. /// public int ProgressPercentage { get; set; } /// /// Gets or sets the Last Scanned Source /// This object contains information about the scanned source. /// public Source ScannedSource { get { return this.scannedSource; } set { this.scannedSource = value; this.NotifyOfPropertyChange("ScannedSource"); } } /// /// Gets or sets SelectedPreset. /// public Preset SelectedPreset { get { return this.selectedPreset; } set { this.selectedPreset = value; this.NotifyOfPropertyChange(() => this.SelectedPreset); } } /// /// Gets or sets a value indicating whether ShowStatusWindow. /// public bool ShowStatusWindow { get { return this.showStatusWindow; } set { this.showStatusWindow = value; this.NotifyOfPropertyChange(() => this.ShowStatusWindow); } } /// /// Gets or sets the Source Label /// This indicates the status of scans. /// public string SourceLabel { get { return string.IsNullOrEmpty(this.sourceLabel) ? "Select 'Source' to continue" : this.sourceLabel; } set { if (!Equals(this.sourceLabel, value)) { this.sourceLabel = value; this.NotifyOfPropertyChange("SourceLabel"); } } } /// /// Gets SourceName. /// public string SourceName { get { // Sanity Check if (this.ScannedSource == null || this.ScannedSource.ScanPath == null) { return string.Empty; } // The title that is selected has a source name. This means it's part of a batch scan. // if (selectedTitle != null && !string.IsNullOrEmpty(selectedTitle.SourceName)) // { // return Path.GetFileNameWithoutExtension(selectedTitle.SourceName); // } // Check if we have a Folder, if so, check if it's a DVD / Bluray drive and get the label. if (this.ScannedSource.ScanPath.EndsWith("\\")) { foreach (DriveInformation item in GeneralUtilities.GetDrives()) { if (item.RootDirectory.Contains(this.ScannedSource.ScanPath)) { return item.VolumeLabel; } } } if (Path.GetFileNameWithoutExtension(this.ScannedSource.ScanPath) != "VIDEO_TS") { return Path.GetFileNameWithoutExtension(this.ScannedSource.ScanPath); } return Path.GetFileNameWithoutExtension(Path.GetDirectoryName(this.ScannedSource.ScanPath)); } } /// /// Gets or sets the Program Status Toolbar Label /// This indicates the status of HandBrake /// public string StatusLabel { get { return string.IsNullOrEmpty(this.statusLabel) ? "Ready" : this.statusLabel; } set { if (!Equals(this.statusLabel, value)) { this.statusLabel = value; this.NotifyOfPropertyChange(() => this.StatusLabel); } } } /// /// Gets or sets the selected titles. /// public BindingList TitleList { get; set; } #endregion #region Public Methods /// /// The Destination Path /// public void BrowseDestination() { var saveFileDialog = new SaveFileDialog { Filter = "mp4|*.mp4;*.m4v|mkv|*.mkv", CheckPathExists = true, AddExtension = true, DefaultExt = ".mp4", OverwritePrompt = true, }; saveFileDialog.ShowDialog(); this.OutputDirectory = Path.GetDirectoryName(saveFileDialog.FileName); } /// /// Cancel a Scan /// public void CancelScan() { this.scanService.Stop(); } /// /// File Scan /// public void FileScan() { var dialog = new VistaOpenFileDialog { Filter = "All files (*.*)|*.*" }; dialog.ShowDialog(); this.StartScan(dialog.FileName, 0); } /// /// Support dropping a file onto the main window to scan. /// /// /// The DragEventArgs. /// public void FilesDroppedOnWindow(DragEventArgs e) { if (e.Data.GetDataPresent(DataFormats.FileDrop)) { var fileNames = e.Data.GetData(DataFormats.FileDrop, true) as string[]; if (fileNames != null && fileNames.Any() && (File.Exists(fileNames[0]) || Directory.Exists(fileNames[0]))) { this.StartScan(fileNames[0], 0); } } e.Handled = true; } /// /// Folder Scan /// public void FolderScan() { var dialog = new VistaFolderBrowserDialog { Description = "Please select a folder.", UseDescriptionForTitle = true }; dialog.ShowDialog(); this.StartScan(dialog.SelectedPath, 0); } /// /// Launch the Help pages. /// public void LaunchHelp() { Process.Start("https://trac.handbrake.fr/wiki/HandBrakeGuide"); } /// /// The on load. /// public override void OnLoad() { // Perform an update check if required // this.updateService.PerformStartupUpdateCheck(this.HandleUpdateCheckResults); // Setup the presets. this.presetService.Load(); if (this.presetService.CheckIfPresetsAreOutOfDate()) { if (!this.userSettingService.GetUserSetting(UserSettingConstants.PresetNotification)) { this.errorService.ShowMessageBox( "HandBrake has determined your built-in presets are out of date... These presets will now be updated." + Environment.NewLine + "Your custom presets have not been updated so you may have to re-create these by deleting and re-adding them.", "Preset Update", MessageBoxButton.OK, MessageBoxImage.Information); } } this.SelectedPreset = this.presetService.DefaultPreset; // Log Cleaning if (this.userSettingService.GetUserSetting(UserSettingConstants.ClearOldLogs)) { var clearLog = new Thread(() => GeneralUtilities.ClearLogFiles(30)); clearLog.Start(); } base.OnLoad(); } /// /// Open the About Window /// public void OpenAboutApplication() { var command = new OpenOptionsScreenCommand(); command.Execute(OptionsTab.About); } /// /// Open the Log Window /// public void OpenLogWindow() { Window window = Application.Current.Windows.Cast().FirstOrDefault(x => x.GetType() == typeof(LogView)); if (window != null) { var logvm = (ILogViewModel)window.DataContext; logvm.SelectedTab = this.IsEncoding ? 0 : 1; window.Activate(); } else { var logvm = IoC.Get(); logvm.SelectedTab = this.IsEncoding ? 0 : 1; this.WindowManager.ShowWindow(logvm); } } /// /// Open the Options Window /// public void OpenOptionsWindow() { this.shellViewModel.DisplayWindow(ShellWindow.OptionsWindow); } /// /// The order by duration. /// public void OrderByDuration() { this.TitleList = new BindingList(this.TitleList.OrderByDescending(o => o.Title.Duration).ToList()); this.NotifyOfPropertyChange(() => this.TitleList); this.OrderedByTitle = false; this.OrderedByDuration = true; } /// /// The order by title. /// public void OrderByTitle() { this.TitleList = new BindingList(this.TitleList.OrderBy(o => o.Title.TitleNumber).ToList()); this.NotifyOfPropertyChange(() => this.TitleList); this.OrderedByTitle = true; this.OrderedByDuration = false; } /// /// Pause an Encode /// public void PauseEncode() { this.queueProcessor.Pause(); } /// /// The select all. /// public void SelectAll() { foreach (SelectionTitle item in this.TitleList) { item.IsSelected = true; } } /// /// The setup. /// /// /// The scanned source. /// public void Setup(Source scannedSource) { this.TitleList.Clear(); if (scannedSource != null) { IEnumerable titles = this.orderedByTitle ? scannedSource.Titles : scannedSource.Titles.OrderByDescending(o => o.Duration).ToList(); foreach (Title item in titles) { var title = new SelectionTitle(item, item.SourceName) { IsSelected = true }; this.TitleList.Add(title); } } } /// <summary> /// Shutdown this View /// </summary> public void Shutdown() { // Shutdown Service this.encodeService.Shutdown(); // Unsubscribe from Events. this.scanService.ScanStared -= this.ScanStared; this.scanService.ScanCompleted -= this.ScanCompleted; this.scanService.ScanStatusChanged -= this.ScanStatusChanged; this.queueProcessor.QueueCompleted -= this.QueueCompleted; this.queueProcessor.QueueChanged -= this.QueueChanged; this.queueProcessor.JobProcessingStarted -= this.QueueProcessorJobProcessingStarted; this.queueProcessor.EncodeService.EncodeStatusChanged -= this.EncodeStatusChanged; } /// <summary> /// Start an Encode /// </summary> public void StartEncode() { // if (this.queueProcessor.IsProcessing) // { // this.errorService.ShowMessageBox("HandBrake is already encoding.", Resources.Error, MessageBoxButton.OK, MessageBoxImage.Error); // return; // } //// Check if we already have jobs, and if we do, just start the queue. // if (this.queueProcessor.Count != 0) // { // this.queueProcessor.Start(); // return; // } //// Otherwise, perform Santiy Checking then add to the queue and start if everything is ok. // if (this.SelectedTitle == null) // { // this.errorService.ShowMessageBox("You must first scan a source.", Resources.Error, MessageBoxButton.OK, MessageBoxImage.Error); // return; // } // if (string.IsNullOrEmpty(this.Destination)) // { // this.errorService.ShowMessageBox("The Destination field was empty.", Resources.Error, MessageBoxButton.OK, MessageBoxImage.Error); // return; // } // if (File.Exists(this.Destination)) // { // MessageBoxResult result = this.errorService.ShowMessageBox("The current file already exists, do you wish to overwrite it?", "Question", MessageBoxButton.YesNo, MessageBoxImage.Question); // if (result == MessageBoxResult.No) // { // return; // } // } //// Create the Queue Task and Start Processing // QueueTask task = new QueueTask // { // Task = new EncodeTask(this.CurrentTask), // CustomQuery = false // }; // this.queueProcessor.Add(task); // this.queueProcessor.Start(); // this.IsEncoding = true; } /// <summary> /// Start a Scan /// </summary> /// <param name="filename"> /// The filename. /// </param> /// <param name="title"> /// The title. /// </param> public void StartScan(string filename, int title) { if (!string.IsNullOrEmpty(filename)) { this.scanService.Scan( filename, title, null, HBConfigurationFactory.Create()); } } /// <summary> /// Stop an Encode. /// </summary> public void StopEncode() { this.queueProcessor.Pause(); this.encodeService.Stop(); } /// <summary> /// The select all. /// </summary> public void UnSelectAll() { foreach (SelectionTitle item in this.TitleList) { item.IsSelected = false; } } #endregion #region Methods /// <summary> /// The Encode Status has changed Handler /// </summary> /// <param name="sender"> /// The Sender /// </param> /// <param name="e"> /// The Encode Progress Event Args /// </param> private void EncodeStatusChanged(object sender, EncodeProgressEventArgs e) { int percent; int.TryParse(Math.Round(e.PercentComplete).ToString(CultureInfo.InvariantCulture), out percent); Execute.OnUIThread( () => { if (this.queueProcessor.EncodeService.IsEncoding) { string josPending = string.Empty; if (!AppArguments.IsInstantHandBrake) { josPending = ", Pending Jobs {5}"; } this.ProgramStatusLabel = string.Format( "{0:00.00}%, FPS: {1:000.0}, Avg FPS: {2:000.0}, Time Remaining: {3}, Elapsed: {4:hh\\:mm\\:ss}" + josPending, e.PercentComplete, e.CurrentFrameRate, e.AverageFrameRate, e.EstimatedTimeLeft, e.ElapsedTime, this.queueProcessor.Count); if (this.lastEncodePercentage != percent && this.windowsSeven.IsWindowsSeven) { this.windowsSeven.SetTaskBarProgress(percent); } this.lastEncodePercentage = percent; this.ProgressPercentage = percent; this.NotifyOfPropertyChange(() => this.ProgressPercentage); } else { this.ProgramStatusLabel = "Queue Finished"; this.IsEncoding = false; if (this.windowsSeven.IsWindowsSeven) { this.windowsSeven.SetTaskBarProgressToNoProgress(); } } }); } /// <summary> /// The queue changed. /// </summary> /// <param name="sender"> /// The sender. /// </param> /// <param name="e"> /// The EventArgs. /// </param> private void QueueChanged(object sender, EventArgs e) { Execute.OnUIThread( () => { this.ProgramStatusLabel = string.Format("{0} Encodes Pending", this.queueProcessor.Count); }); } /// <summary> /// The Queue has completed handler /// </summary> /// <param name="sender"> /// The Sender /// </param> /// <param name="e"> /// The EventArgs /// </param> private void QueueCompleted(object sender, EventArgs e) { this.IsEncoding = false; Execute.OnUIThread( () => { this.ProgramStatusLabel = "Queue Finished"; this.IsEncoding = false; if (this.windowsSeven.IsWindowsSeven) { this.windowsSeven.SetTaskBarProgressToNoProgress(); } }); } /// <summary> /// Handle the Queue Starting Event /// </summary> /// <param name="sender"> /// The sender. /// </param> /// <param name="e"> /// The e. /// </param> private void QueueProcessorJobProcessingStarted(object sender, QueueProgressEventArgs e) { Execute.OnUIThread( () => { this.ProgramStatusLabel = "Preparing to encode ..."; this.IsEncoding = true; }); } /// <summary> /// Handle the Scan Completed Event /// </summary> /// <param name="sender"> /// The Sender /// </param> /// <param name="e"> /// The EventArgs /// </param> private void ScanCompleted(object sender, ScanCompletedEventArgs e) { this.scanService.SouceData.CopyTo(this.ScannedSource); this.NotifyOfPropertyChange(() => this.ScannedSource); Execute.OnUIThread( () => { if (this.scannedSource != null) { this.Setup(this.scannedSource); } if (e.Successful) { this.NotifyOfPropertyChange(() => this.ScannedSource); this.NotifyOfPropertyChange(() => this.ScannedSource.Titles); } this.ShowStatusWindow = false; if (e.Successful) { this.SourceLabel = this.SourceName; this.StatusLabel = "Scan Completed"; } else if (e.Cancelled) { this.SourceLabel = "Scan Cancelled."; this.StatusLabel = "Scan Cancelled."; } else if (e.Exception == null && e.ErrorInformation != null) { this.SourceLabel = "Scan failed: " + e.ErrorInformation; this.StatusLabel = "Scan failed: " + e.ErrorInformation; } else { this.SourceLabel = "Scan Failed... See Activity Log for details."; this.StatusLabel = "Scan Failed... See Activity Log for details."; } }); } /// <summary> /// Handle the Scan Started Event /// </summary> /// <param name="sender"> /// The Sender /// </param> /// <param name="e"> /// The EventArgs /// </param> private void ScanStared(object sender, EventArgs e) { Execute.OnUIThread( () => { this.StatusLabel = "Scanning source, please wait..."; this.ShowStatusWindow = true; }); } /// <summary> /// Handle the Scan Status Changed Event. /// </summary> /// <param name="sender"> /// The Sender /// </param> /// <param name="e"> /// The EventArgs /// </param> private void ScanStatusChanged(object sender, ScanProgressEventArgs e) { this.SourceLabel = string.Format("Scanning Title {0} of {1} ({2}%)", e.CurrentTitle, e.Titles, e.Percentage); this.StatusLabel = string.Format("Scanning Title {0} of {1} ({2}%)", e.CurrentTitle, e.Titles, e.Percentage); } #endregion } }