// --------------------------------------------------------------------------------------------------------------------
//
// 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 HandBrakeWPF.Factories;
using HandBrakeWPF.Properties;
using HandBrakeWPF.Services.Interfaces;
using HandBrakeWPF.Services.Queue.Model;
using HandBrakeWPF.Services.Scan.Interfaces;
using HandBrakeWPF.Services.Scan.Model;
using HandBrakeWPF.ViewModels.Interfaces;
using EncodeCompletedEventArgs = HandBrakeWPF.Services.Encode.EventArgs.EncodeCompletedEventArgs;
using EncodeProgressEventArgs = HandBrakeWPF.Services.Encode.EventArgs.EncodeProgressEventArgs;
using EncodeTask = HandBrakeWPF.Services.Encode.Model.EncodeTask;
using IEncode = HandBrakeWPF.Services.Encode.Interfaces.IEncode;
using LibEncode = HandBrakeWPF.Services.Encode.LibEncode;
using OutputFormat = HandBrakeWPF.Services.Encode.Model.Models.OutputFormat;
using PointToPointMode = HandBrakeWPF.Services.Encode.Model.Models.PointToPointMode;
///
/// 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 = Resources.StaticPreviewViewModel_Title;
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(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);
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(Resources.StaticPreviewViewModel_UnableToFindVLC,
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(Resources.StaticPreviewViewModel_UnableToPlayFile,
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(Resources.StaticPreviewViewModel_AlreadyEncoding,
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
}
}