// -------------------------------------------------------------------------------------------------------------------- // // This file is part of the HandBrake source code - It may be used under the terms of the GNU General Public License. // // // The Static Preview View Model // // -------------------------------------------------------------------------------------------------------------------- namespace HandBrakeWPF.ViewModels { using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Runtime.ExceptionServices; using System.Threading; using System.Windows; using System.Windows.Media.Imaging; using HandBrake.ApplicationServices.Interop.Model.Encoding; using HandBrake.ApplicationServices.Services.Encode; using HandBrake.ApplicationServices.Services.Encode.EventArgs; using HandBrake.ApplicationServices.Services.Encode.Interfaces; using HandBrake.ApplicationServices.Services.Encode.Model; using HandBrake.ApplicationServices.Services.Encode.Model.Models; using HandBrake.ApplicationServices.Services.Scan.Interfaces; using HandBrake.ApplicationServices.Services.Scan.Model; using HandBrakeWPF.Factories; using HandBrakeWPF.Properties; using HandBrakeWPF.Services.Interfaces; using HandBrakeWPF.Services.Queue.Model; using HandBrakeWPF.ViewModels.Interfaces; /// /// The Static Preview View Model /// public class StaticPreviewViewModel : ViewModelBase, IStaticPreviewViewModel { /* * TODO * - Window Size / Scale to screen etc. */ #region Fields /// /// The scan service. /// private readonly IScan scanService; /// /// Backing field for the encode service. /// private readonly IEncode encodeService; /// /// The error service /// private readonly IErrorService errorService; /// /// The user Setting Service /// private readonly IUserSettingService userSettingService; /// /// The height. /// private int height; /// /// The preview image. /// private BitmapImage previewImage; /// /// The selected preview image. /// private int selectedPreviewImage; /// /// The width. /// private int width; /// /// The preview not available. /// private bool previewNotAvailable; /// /// The percentage. /// private string percentage; /// /// The percentage value. /// private double percentageValue; /// /// The Backing field for IsEncoding /// private bool isEncoding; /// /// Backing field for use system default player /// private bool useSystemDefaultPlayer; #endregion #region Constructors and Destructors /// /// Initializes a new instance of the class. /// /// /// The scan service. /// /// /// The user Setting Service. /// /// /// The error Service. /// public StaticPreviewViewModel(IScan scanService, IUserSettingService userSettingService, IErrorService errorService) { this.scanService = scanService; this.selectedPreviewImage = 1; this.Title = Resources.Preview; this.PreviewNotAvailable = true; // Live Preview this.userSettingService = userSettingService; this.errorService = errorService; this.encodeService = new LibEncode(); // Preview needs a seperate instance rather than the shared singleton. This could maybe do with being refactored at some point this.Title = "Preview"; this.Percentage = "0.00%"; this.PercentageValue = 0; this.Duration = 30; this.CanPlay = true; UseSystemDefaultPlayer = userSettingService.GetUserSetting(UserSettingConstants.DefaultPlayer); this.Duration = userSettingService.GetUserSetting(UserSettingConstants.LastPreviewDuration); } #endregion #region Public Properties /// /// Gets or sets the height. /// public int Height { get { return this.height; } set { if (value == this.height) { return; } this.height = value; this.NotifyOfPropertyChange(() => this.Height); } } /// /// Gets or sets the preview image. /// public BitmapImage PreviewImage { get { return this.previewImage; } set { if (Equals(value, this.previewImage)) { return; } this.previewImage = value; this.NotifyOfPropertyChange(() => this.PreviewImage); } } /// /// Gets or sets the selected preview image. /// public int SelectedPreviewImage { get { return this.selectedPreviewImage; } set { if (value == this.selectedPreviewImage) { return; } this.selectedPreviewImage = value; this.NotifyOfPropertyChange(() => this.SelectedPreviewImage); this.UpdatePreviewFrame(); } } /// /// Gets or sets the task. /// public EncodeTask Task { get; set; } /// /// Gets or sets the scanned source. /// public Source ScannedSource { get; set; } /// /// Gets the total previews. /// public int TotalPreviews { get { return this.UserSettingService.GetUserSetting(UserSettingConstants.PreviewScanCount) - 1; } } /// /// Gets or sets the width. /// public int Width { get { return this.width; } set { if (value == this.width) { return; } this.width = value; this.NotifyOfPropertyChange(() => this.Width); } } /// /// Gets or sets a value indicating whether preview not available. /// public bool PreviewNotAvailable { get { return this.previewNotAvailable; } set { if (value.Equals(this.previewNotAvailable)) { return; } this.previewNotAvailable = value; this.NotifyOfPropertyChange(() => this.PreviewNotAvailable); } } #endregion #region LivePreviewProperties /// /// Gets AvailableDurations. /// public IEnumerable AvailableDurations { get { return new List { 5, 10, 30, 45, 60, 75, 90, 105, 120, 150, 180, 210, 240 }; } } /// /// Gets or sets Duration. /// public int Duration { get; set; } /// /// Gets or sets Percentage. /// public string Percentage { get { return this.percentage; } set { this.percentage = value; this.NotifyOfPropertyChange(() => this.Percentage); } } /// /// Gets or sets PercentageValue. /// public double PercentageValue { get { return this.percentageValue; } set { this.percentageValue = value; this.NotifyOfPropertyChange(() => this.PercentageValue); } } /// /// Gets StartPoints. /// public IEnumerable StartPoints { get { List startPoints = new List(); for (int i = 1; i <= this.UserSettingService.GetUserSetting(UserSettingConstants.PreviewScanCount); i++) { startPoints.Add(i); } return startPoints; } } /// /// Gets or sets a value indicating whether UseSystemDefaultPlayer. /// public bool UseSystemDefaultPlayer { get { return this.useSystemDefaultPlayer; } set { this.useSystemDefaultPlayer = value; this.NotifyOfPropertyChange(() => UseSystemDefaultPlayer); this.userSettingService.SetUserSetting(UserSettingConstants.DefaultPlayer, value); } } /// /// Gets or sets a value indicating whether IsEncoding. /// public bool IsEncoding { get { return this.isEncoding; } set { this.isEncoding = value; this.CanPlay = !value; this.NotifyOfPropertyChange(() => this.CanPlay); this.NotifyOfPropertyChange(() => this.IsEncoding); } } /// /// Gets or sets the Currently Playing / Encoding Filename. /// public string CurrentlyPlaying { get; set; } /// /// Gets or sets a value indicating whether can play. /// public bool CanPlay { get; set; } #endregion #region Public Methods and Operators /// /// The update preview frame. /// /// /// The task. /// /// /// The scanned Source. /// public void UpdatePreviewFrame(EncodeTask task, Source scannedSource) { this.Task = task; this.UpdatePreviewFrame(); this.DisplayName = "Picture Preview"; this.Title = Resources.Preview; this.ScannedSource = scannedSource; } /// /// Gets or sets a value indicating whether is open. /// public bool IsOpen { get; set; } /// /// The update preview frame. /// [HandleProcessCorruptedStateExceptions] public void UpdatePreviewFrame() { // Don't preview for small images. if (this.Task.Anamorphic == Anamorphic.Loose && this.Task.Width < 32) { PreviewNotAvailable = true; return; } if ((this.Task.Anamorphic == Anamorphic.None || this.Task.Anamorphic == Anamorphic.Custom) && (this.Task.Width < 32 || this.Task.Height < 32)) { PreviewNotAvailable = true; return; } BitmapImage image = null; try { image = this.scanService.GetPreview(this.Task, this.SelectedPreviewImage, HBConfigurationFactory.Create()); } catch (Exception exc) { PreviewNotAvailable = true; Debug.WriteLine(exc); } if (image != null) { PreviewNotAvailable = false; this.Width = (int)Math.Ceiling(image.Width); this.Height = (int)Math.Ceiling(image.Height); this.PreviewImage = image; } } /// /// The preview size changed. /// /// /// The ea. /// public void PreviewSizeChanged(SizeChangedEventArgs ea) { // TODO implement window size scaling here. Rect workArea = SystemParameters.WorkArea; if (ea.NewSize.Width > workArea.Width) { this.Width = (int)Math.Round(workArea.Width, 0) - 20; this.Title = Resources.Preview_Scaled; } if (ea.NewSize.Height > workArea.Height) { this.Height = (int)Math.Round(workArea.Height, 0) - 20; this.Title = Resources.Preview_Scaled; } } #endregion #region Public Method - Live Preview #region Public Methods /// /// Close this window. /// public void Close() { this.TryClose(); } /// /// Handle The Initialisation /// public override void OnLoad() { } /// /// Encode and play a sample /// public void Play() { try { this.IsEncoding = true; if (File.Exists(this.CurrentlyPlaying)) File.Delete(this.CurrentlyPlaying); } catch (Exception) { this.IsEncoding = false; this.errorService.ShowMessageBox("Unable to delete previous preview file. You may need to restart the application.", Resources.Error, MessageBoxButton.OK, MessageBoxImage.Error); } if (this.Task == null || string.IsNullOrEmpty(Task.Source)) { this.errorService.ShowMessageBox("You must first scan a source and setup your encode before creating a preview.", Resources.Error, MessageBoxButton.OK, MessageBoxImage.Error); return; } EncodeTask encodeTask = new EncodeTask(this.Task) { PreviewEncodeDuration = this.Duration, PreviewEncodeStartAt = this.SelectedPreviewImage, PointToPointMode = PointToPointMode.Preview }; // Filename handling. if (string.IsNullOrEmpty(encodeTask.Destination)) { string filename = Path.ChangeExtension(Path.GetTempFileName(), encodeTask.OutputFormat == OutputFormat.Mkv ? "m4v" : "mkv"); encodeTask.Destination = filename; this.CurrentlyPlaying = filename; } else { string directory = Path.GetDirectoryName(encodeTask.Destination) ?? string.Empty; string filename = Path.GetFileNameWithoutExtension(encodeTask.Destination); string extension = Path.GetExtension(encodeTask.Destination); string previewFilename = string.Format("{0}_preview{1}", filename, extension); string previewFullPath = Path.Combine(directory, previewFilename); encodeTask.Destination = previewFullPath; this.CurrentlyPlaying = previewFullPath; } // Setup the encode task as a preview encode encodeTask.IsPreviewEncode = true; encodeTask.PreviewEncodeStartAt = this.SelectedPreviewImage; // TODO 0 and 1 mean the same. Need to fix this as it knocks the video out of sync with the still preview. encodeTask.PreviewEncodeDuration = this.Duration; QueueTask task = new QueueTask(encodeTask, HBConfigurationFactory.Create(), this.ScannedSource.ScanPath); ThreadPool.QueueUserWorkItem(this.CreatePreview, task); } #endregion #region Private Methods /// /// Play the Encoded file /// private void PlayFile() { // Launch VLC and Play video. if (this.CurrentlyPlaying != string.Empty) { if (File.Exists(this.CurrentlyPlaying)) { string args = "\"" + this.CurrentlyPlaying + "\""; if (this.UseSystemDefaultPlayer) { Process.Start(args); } else { if (!File.Exists(UserSettingService.GetUserSetting(UserSettingConstants.VLCPath))) { // Attempt to find VLC if it doesn't exist in the default set location. string vlcPath; if (8 == IntPtr.Size || (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("PROCESSOR_ARCHITEW6432")))) vlcPath = Environment.GetEnvironmentVariable("ProgramFiles(x86)"); else vlcPath = Environment.GetEnvironmentVariable("ProgramFiles"); if (!string.IsNullOrEmpty(vlcPath)) { vlcPath = Path.Combine(vlcPath, "VideoLAN\\VLC\\vlc.exe"); } if (File.Exists(vlcPath)) { UserSettingService.SetUserSetting(UserSettingConstants.VLCPath, vlcPath); } else { this.errorService.ShowMessageBox("Unable to detect VLC Player. \nPlease make sure VLC is installed and the directory specified in HandBrake's options is correct. (See: \"Tools Menu > Options > Picture Tab\")", Resources.Error, MessageBoxButton.OK, MessageBoxImage.Warning); } } if (File.Exists(UserSettingService.GetUserSetting(UserSettingConstants.VLCPath))) { ProcessStartInfo vlc = new ProcessStartInfo(UserSettingService.GetUserSetting(UserSettingConstants.VLCPath), args); Process.Start(vlc); } } } else { this.errorService.ShowMessageBox("Unable to find the preview file. Either the file was deleted or the encode failed. Check the activity log for details.", Resources.Error, MessageBoxButton.OK, MessageBoxImage.Warning); } } } /// /// Create the Preview. /// /// /// The state. /// private void CreatePreview(object state) { // Make sure we are not already encoding and if we are then display an error. if (encodeService.IsEncoding) { this.errorService.ShowMessageBox("Handbrake is already encoding a video! Only one file can be encoded at any one time.", Resources.Error, MessageBoxButton.OK, MessageBoxImage.Error); return; } this.encodeService.EncodeCompleted += this.encodeService_EncodeCompleted; this.encodeService.EncodeStatusChanged += this.encodeService_EncodeStatusChanged; this.encodeService.Start(((QueueTask)state).Task, ((QueueTask)state).Configuration); this.userSettingService.SetUserSetting(UserSettingConstants.LastPreviewDuration, this.Duration); } #endregion #region Event Handlers /// /// Handle Encode Progress Events /// /// /// The sender. /// /// /// The EncodeProgressEventArgs. /// private void encodeService_EncodeStatusChanged(object sender, EncodeProgressEventArgs e) { this.Percentage = string.Format("{0} %", Math.Round(e.PercentComplete, 2).ToString(CultureInfo.InvariantCulture)); this.PercentageValue = e.PercentComplete; } /// /// Handle the Encode Completed Event /// /// /// The sender. /// /// /// The EncodeCompletedEventArgs. /// private void encodeService_EncodeCompleted(object sender, EncodeCompletedEventArgs e) { this.Percentage = "0.00%"; this.PercentageValue = 0; this.IsEncoding = false; this.encodeService.EncodeCompleted -= this.encodeService_EncodeCompleted; this.encodeService.EncodeStatusChanged -= this.encodeService_EncodeStatusChanged; this.PlayFile(); } #endregion #endregion } }