// --------------------------------------------------------------------------------------------------------------------
//
// 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.Interop.Interop.Model.Encoding;
using HandBrakeWPF.Factories;
using HandBrakeWPF.Properties;
using HandBrakeWPF.Services.Encode.Model.Models;
using HandBrakeWPF.Services.Interfaces;
using HandBrakeWPF.Services.Logging.Interfaces;
using HandBrakeWPF.Services.Queue.Model;
using HandBrakeWPF.Services.Scan.Interfaces;
using HandBrakeWPF.Services.Scan.Model;
using HandBrakeWPF.Utilities;
using HandBrakeWPF.ViewModels.Interfaces;
using EncodeCompletedEventArgs = Services.Encode.EventArgs.EncodeCompletedEventArgs;
using EncodeProgressEventArgs = Services.Encode.EventArgs.EncodeProgressEventArgs;
using EncodeTask = Services.Encode.Model.EncodeTask;
using IEncode = Services.Encode.Interfaces.IEncode;
using LibEncode = Services.Encode.LibEncode;
using OutputFormat = Services.Encode.Model.Models.OutputFormat;
using PointToPointMode = Services.Encode.Model.Models.PointToPointMode;
public class StaticPreviewViewModel : ViewModelBase, IStaticPreviewViewModel
{
private readonly IScan scanService;
private readonly IErrorService errorService;
private readonly ILog logService;
private readonly ILogInstanceManager logInstanceManager;
private readonly IPortService portService;
private readonly IUserSettingService userSettingService;
private IEncode encodeService;
private int height;
private BitmapSource previewImage;
private int selectedPreviewImage;
private int width;
private bool previewNotAvailable;
private string percentage;
private double percentageValue;
private bool isEncoding;
private bool useSystemDefaultPlayer;
private bool previewRotateFlip;
private bool showPictureSettingControls;
public StaticPreviewViewModel(IScan scanService, IUserSettingService userSettingService, IErrorService errorService, ILog logService, ILogInstanceManager logInstanceManager, IPortService portService)
{
this.scanService = scanService;
this.selectedPreviewImage = 1;
this.Title = Resources.Preview;
this.PreviewNotAvailable = true;
// Live Preview
this.userSettingService = userSettingService;
this.errorService = errorService;
this.logService = logService;
this.logInstanceManager = logInstanceManager;
this.portService = portService;
this.Title = "Preview";
this.Percentage = "0.00%";
this.PercentageValue = 0;
this.Duration = 30;
this.CanPlay = true;
this.useSystemDefaultPlayer = userSettingService.GetUserSetting(UserSettingConstants.DefaultPlayer);
this.showPictureSettingControls = userSettingService.GetUserSetting(UserSettingConstants.PreviewShowPictureSettingsOverlay);
this.Duration = userSettingService.GetUserSetting(UserSettingConstants.LastPreviewDuration);
this.previewRotateFlip = userSettingService.GetUserSetting(UserSettingConstants.PreviewRotationFlip);
this.NotifyOfPropertyChange(() => this.previewRotateFlip); // Don't want to trigger an Update, so setting the backing variable.
}
public IPictureSettingsViewModel PictureSettingsViewModel { get; private set; }
public int Height
{
get
{
return this.height;
}
set
{
if (value == this.height)
{
return;
}
this.height = this.FixHeight(value);
this.NotifyOfPropertyChange(() => this.Height);
}
}
public BitmapSource PreviewImage
{
get
{
return this.previewImage;
}
set
{
if (Equals(value, this.previewImage))
{
return;
}
this.previewImage = value;
this.NotifyOfPropertyChange(() => this.PreviewImage);
}
}
public int SelectedPreviewImage
{
get
{
return this.selectedPreviewImage;
}
set
{
if (value == this.selectedPreviewImage)
{
return;
}
this.selectedPreviewImage = value;
this.NotifyOfPropertyChange(() => this.SelectedPreviewImage);
this.UpdatePreviewFrame();
}
}
public bool PreviewRotateFlip
{
get => this.previewRotateFlip;
set
{
if (value == this.previewRotateFlip)
{
return;
}
this.previewRotateFlip = value;
this.NotifyOfPropertyChange(() => this.PreviewRotateFlip);
this.UpdatePreviewFrame();
this.userSettingService.SetUserSetting(UserSettingConstants.PreviewRotationFlip, value);
}
}
public EncodeTask Task { get; set; }
public Source ScannedSource { get; set; }
public int TotalPreviews
{
get
{
return this.userSettingService.GetUserSetting(UserSettingConstants.PreviewScanCount) - 1;
}
}
public int Width
{
get
{
return this.width;
}
set
{
if (value == this.width)
{
return;
}
this.width = this.FixWidth(value);
this.NotifyOfPropertyChange(() => this.Width);
}
}
public bool PreviewNotAvailable
{
get
{
return this.previewNotAvailable;
}
set
{
if (value.Equals(this.previewNotAvailable))
{
return;
}
this.previewNotAvailable = value;
this.NotifyOfPropertyChange(() => this.PreviewNotAvailable);
}
}
public IEnumerable AvailableDurations
{
get
{
return new List { 5, 10, 30, 45, 60, 75, 90, 105, 120, 150, 180, 210, 240 };
}
}
public int Duration { get; set; }
public string Percentage
{
get
{
return this.percentage;
}
set
{
this.percentage = value;
this.NotifyOfPropertyChange(() => this.Percentage);
}
}
public double PercentageValue
{
get
{
return this.percentageValue;
}
set
{
this.percentageValue = value;
this.NotifyOfPropertyChange(() => this.PercentageValue);
}
}
public IEnumerable StartPoints
{
get
{
List startPoints = new List();
for (int i = 1;
i <= this.userSettingService.GetUserSetting(UserSettingConstants.PreviewScanCount);
i++)
{
startPoints.Add(i);
}
return startPoints;
}
}
public bool UseSystemDefaultPlayer
{
get
{
return this.useSystemDefaultPlayer;
}
set
{
this.useSystemDefaultPlayer = value;
this.NotifyOfPropertyChange(() => UseSystemDefaultPlayer);
this.userSettingService.SetUserSetting(UserSettingConstants.DefaultPlayer, value);
}
}
public bool IsEncoding
{
get
{
return this.isEncoding;
}
set
{
this.isEncoding = value;
this.CanPlay = !value;
this.NotifyOfPropertyChange(() => this.CanPlay);
this.NotifyOfPropertyChange(() => this.IsEncoding);
}
}
public string CurrentlyPlaying { get; set; }
public bool CanPlay { get; set; }
public bool IsOpen { get; set; }
public bool ShowPictureSettingControls
{
get => this.showPictureSettingControls;
set
{
this.showPictureSettingControls = value;
this.NotifyOfPropertyChange(() => this.ShowPictureSettingControls);
this.userSettingService.SetUserSetting(UserSettingConstants.PreviewShowPictureSettingsOverlay, value);
}
}
public void UpdatePreviewFrame(EncodeTask task, Source scannedSource)
{
this.Task = task;
this.UpdatePreviewFrame();
this.DisplayName = Resources.StaticPreviewViewModel_Title;
this.Title = Resources.Preview;
this.ScannedSource = scannedSource;
}
public void NextPreview()
{
int maxPreview = this.userSettingService.GetUserSetting(UserSettingConstants.PreviewScanCount);
if ((this.SelectedPreviewImage + 1) == maxPreview)
{
return;
}
this.SelectedPreviewImage = this.SelectedPreviewImage + 1;
}
public void PreviousPreview()
{
if (this.SelectedPreviewImage < 1)
{
return;
}
this.SelectedPreviewImage = this.SelectedPreviewImage - 1;
}
[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;
}
BitmapSource image = null;
try
{
image = this.scanService.GetPreview(this.Task, this.SelectedPreviewImage);
}
catch (Exception exc)
{
PreviewNotAvailable = true;
Debug.WriteLine(exc);
}
if (image != null)
{
if (previewRotateFlip)
{
image = BitmapHelpers.CreateTransformedBitmap(image, this.Task.Rotation, this.Task.FlipVideo);
}
PreviewNotAvailable = false;
this.Width = (int)Math.Ceiling(image.Width);
this.Height = (int)Math.Ceiling(image.Height);
this.PreviewImage = image;
}
}
public int FixWidth(int width)
{
Rect workArea = SystemParameters.WorkArea;
if (width > workArea.Width)
{
return (int)Math.Round(workArea.Width, 0) - 50;
}
return width;
}
public int FixHeight(int height)
{
Rect workArea = SystemParameters.WorkArea;
if (height > workArea.Height)
{
return (int)Math.Round(workArea.Height, 0) - 50;
}
return height;
}
public void Close()
{
this.IsOpen = false;
}
public void SetPictureSettingsInstance(IPictureSettingsViewModel pictureSettingsViewModel)
{
this.PictureSettingsViewModel = pictureSettingsViewModel;
}
public override void OnLoad()
{
}
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);
return;
}
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 formatExtension;
switch (encodeTask.OutputFormat)
{
case OutputFormat.WebM:
formatExtension = "webm";
break;
case OutputFormat.Mp4:
formatExtension = "m4v";
break;
case OutputFormat.Mkv:
default:
formatExtension = "mkv";
break;
}
string filename = Path.ChangeExtension(Path.GetTempFileName(), formatExtension);
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 + 1;
encodeTask.PreviewEncodeDuration = this.Duration;
SubtitleTrack scanTrack = null;
foreach (var track in encodeTask.SubtitleTracks)
{
if (track.SourceTrack != null && track.SourceTrack.SubtitleType == SubtitleType.ForeignAudioSearch)
{
scanTrack = track;
break;
}
}
if (scanTrack != null)
{
encodeTask.SubtitleTracks.Remove(scanTrack);
}
QueueTask task = new QueueTask(encodeTask, HBConfigurationFactory.Create(), this.ScannedSource.ScanPath, null, false);
ThreadPool.QueueUserWorkItem(this.CreatePreview, task);
}
public void CancelEncode()
{
if (this.encodeService.IsEncoding)
{
this.encodeService.Stop();
}
}
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.MediaPlayerPath)))
{
ProcessStartInfo process = new ProcessStartInfo(userSettingService.GetUserSetting(UserSettingConstants.MediaPlayerPath), args);
Process.Start(process);
return;
}
else
{
// Fallback to the System Default
Process.Start(args);
}
}
}
else
{
this.errorService.ShowMessageBox(Resources.StaticPreviewViewModel_UnableToPlayFile,
Resources.Error, MessageBoxButton.OK, MessageBoxImage.Warning);
}
}
}
private void CreatePreview(object state)
{
// Make sure we are not already encoding and if we are then display an error.
if (this.encodeService != null && encodeService.IsEncoding)
{
this.errorService.ShowMessageBox(Resources.StaticPreviewViewModel_AlreadyEncoding, Resources.Error, MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
this.encodeService = new LibEncode(userSettingService, logInstanceManager, 0, portService); // Preview needs a separate instance rather than the shared singleton. This could maybe do with being refactored at some point
this.encodeService.EncodeCompleted += this.encodeService_EncodeCompleted;
this.encodeService.EncodeStatusChanged += this.encodeService_EncodeStatusChanged;
this.encodeService.Start(((QueueTask)state).Task, ((QueueTask)state).Configuration, null);
this.userSettingService.SetUserSetting(UserSettingConstants.LastPreviewDuration, this.Duration);
}
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;
}
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;
if (e.ErrorInformation != "1")
{
this.PlayFile();
}
}
}
}