mirror of https://github.com/HandBrake/HandBrake
1145 lines
39 KiB
C#
1145 lines
39 KiB
C#
// --------------------------------------------------------------------------------------------------------------------
|
|
// <copyright file="PictureSettingsViewModel.cs" company="HandBrake Project (http://handbrake.fr)">
|
|
// This file is part of the HandBrake source code - It may be used under the terms of the GNU General Public License.
|
|
// </copyright>
|
|
// <summary>
|
|
// The Picture Settings View Model
|
|
// </summary>
|
|
// --------------------------------------------------------------------------------------------------------------------
|
|
|
|
namespace HandBrakeWPF.ViewModels
|
|
{
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
|
|
using HandBrake.Interop.Interop;
|
|
using HandBrake.Interop.Interop.Model;
|
|
using HandBrake.Interop.Interop.Model.Encoding;
|
|
|
|
using HandBrakeWPF.EventArgs;
|
|
using HandBrakeWPF.Helpers;
|
|
using HandBrakeWPF.Properties;
|
|
using HandBrakeWPF.Services.Presets.Model;
|
|
using HandBrakeWPF.Services.Scan.Model;
|
|
using HandBrakeWPF.Utilities;
|
|
using HandBrakeWPF.ViewModels.Interfaces;
|
|
|
|
using EncodeTask = HandBrakeWPF.Services.Encode.Model.EncodeTask;
|
|
using PresetPictureSettingsMode = HandBrakeWPF.Model.Picture.PresetPictureSettingsMode;
|
|
|
|
/// <summary>
|
|
/// The Picture Settings View Model
|
|
/// </summary>
|
|
public class PictureSettingsViewModel : ViewModelBase, IPictureSettingsViewModel
|
|
{
|
|
/*
|
|
* TODO:
|
|
* - We are not handling cropping correctly within the UI.
|
|
* - The Height is not correctly set when using no Anamorphic
|
|
* - Maintain Aspect ratio needs corrected.
|
|
* - Custom Anamorphic.
|
|
*
|
|
*/
|
|
#region Constants and Fields
|
|
|
|
/// <summary>
|
|
/// The display size.
|
|
/// </summary>
|
|
private string displaySize;
|
|
|
|
/// <summary>
|
|
/// Backing field for for height control enabled
|
|
/// </summary>
|
|
private bool heightControlEnabled = true;
|
|
|
|
/// <summary>
|
|
/// Backing field for show custom anamorphic controls
|
|
/// </summary>
|
|
private bool showCustomAnamorphicControls;
|
|
|
|
/// <summary>
|
|
/// The source info.
|
|
/// </summary>
|
|
private string sourceInfo;
|
|
|
|
/// <summary>
|
|
/// Source Par Values
|
|
/// </summary>
|
|
private Size sourceParValues;
|
|
|
|
/// <summary>
|
|
/// Source Resolution
|
|
/// </summary>
|
|
private Size sourceResolution;
|
|
|
|
/// <summary>
|
|
/// Backing field for width control enabled.
|
|
/// </summary>
|
|
private bool widthControlEnabled = true;
|
|
|
|
/// <summary>
|
|
/// Backing field for the show modulus field
|
|
/// </summary>
|
|
private bool showModulus;
|
|
|
|
/// <summary>
|
|
/// Backing field for showing display size.
|
|
/// </summary>
|
|
private bool showDisplaySize;
|
|
|
|
/// <summary>
|
|
/// Backing field for max height
|
|
/// </summary>
|
|
private int maxHeight;
|
|
|
|
/// <summary>
|
|
/// Backing field for max width
|
|
/// </summary>
|
|
private int maxWidth;
|
|
|
|
/// <summary>
|
|
/// The show keep ar backing field.
|
|
/// </summary>
|
|
private bool showKeepAr = true;
|
|
|
|
/// <summary>
|
|
/// The delayed previewprocessor.
|
|
/// </summary>
|
|
private DelayedActionProcessor delayedPreviewprocessor = new DelayedActionProcessor();
|
|
|
|
/// <summary>
|
|
/// The current title.
|
|
/// </summary>
|
|
private Title currentTitle;
|
|
|
|
/// <summary>
|
|
/// The scanned source.
|
|
/// </summary>
|
|
private Source scannedSource;
|
|
|
|
#endregion
|
|
|
|
#region Constructors and Destructors
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="HandBrakeWPF.ViewModels.PictureSettingsViewModel"/> class.
|
|
/// </summary>
|
|
public PictureSettingsViewModel(IStaticPreviewViewModel staticPreviewViewModel)
|
|
{
|
|
this.StaticPreviewViewModel = staticPreviewViewModel;
|
|
this.sourceResolution = new Size(0, 0);
|
|
this.Task = new EncodeTask();
|
|
this.Init();
|
|
}
|
|
|
|
#endregion
|
|
|
|
public event EventHandler<TabStatusEventArgs> TabStatusChanged;
|
|
|
|
#region Properties
|
|
|
|
/// <summary>
|
|
/// Gets or sets the static preview view model.
|
|
/// </summary>
|
|
public IStaticPreviewViewModel StaticPreviewViewModel { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets AnamorphicModes.
|
|
/// </summary>
|
|
public IEnumerable<Anamorphic> AnamorphicModes
|
|
{
|
|
get
|
|
{
|
|
return new List<Anamorphic> { Anamorphic.None, Anamorphic.Automatic, Anamorphic.Loose, Anamorphic.Custom };
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets DisplaySize.
|
|
/// </summary>
|
|
public string DisplaySize
|
|
{
|
|
get
|
|
{
|
|
return this.displaySize;
|
|
}
|
|
|
|
set
|
|
{
|
|
this.displaySize = value;
|
|
this.NotifyOfPropertyChange(() => this.DisplaySize);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether HeightControlEnabled.
|
|
/// </summary>
|
|
public bool HeightControlEnabled
|
|
{
|
|
get
|
|
{
|
|
return this.heightControlEnabled;
|
|
}
|
|
|
|
set
|
|
{
|
|
this.heightControlEnabled = value;
|
|
this.NotifyOfPropertyChange(() => this.HeightControlEnabled);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets ModulusValues.
|
|
/// </summary>
|
|
public IEnumerable<int> ModulusValues
|
|
{
|
|
get
|
|
{
|
|
return new List<int> { 16, 8, 4, 2 };
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether ShowCustomAnamorphicControls.
|
|
/// </summary>
|
|
public bool ShowCustomAnamorphicControls
|
|
{
|
|
get
|
|
{
|
|
return this.showCustomAnamorphicControls;
|
|
}
|
|
|
|
set
|
|
{
|
|
this.showCustomAnamorphicControls = value;
|
|
this.NotifyOfPropertyChange(() => this.ShowCustomAnamorphicControls);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets SourceInfo.
|
|
/// </summary>
|
|
public string SourceInfo
|
|
{
|
|
get
|
|
{
|
|
return this.sourceInfo;
|
|
}
|
|
|
|
set
|
|
{
|
|
this.sourceInfo = value;
|
|
this.NotifyOfPropertyChange(() => this.SourceInfo);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets Task.
|
|
/// </summary>
|
|
public EncodeTask Task { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether WidthControlEnabled.
|
|
/// </summary>
|
|
public bool WidthControlEnabled
|
|
{
|
|
get
|
|
{
|
|
return this.widthControlEnabled;
|
|
}
|
|
|
|
set
|
|
{
|
|
this.widthControlEnabled = value;
|
|
this.NotifyOfPropertyChange(() => this.WidthControlEnabled);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether ShowModulus.
|
|
/// </summary>
|
|
public bool ShowModulus
|
|
{
|
|
get
|
|
{
|
|
return this.showModulus;
|
|
}
|
|
set
|
|
{
|
|
this.showModulus = value;
|
|
this.NotifyOfPropertyChange(() => this.ShowModulus);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether ShowDisplaySize.
|
|
/// </summary>
|
|
public bool ShowDisplaySize
|
|
{
|
|
get
|
|
{
|
|
return this.showDisplaySize;
|
|
}
|
|
set
|
|
{
|
|
this.showDisplaySize = value;
|
|
this.NotifyOfPropertyChange(() => this.ShowDisplaySize);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets MaxHeight.
|
|
/// </summary>
|
|
public int MaxHeight
|
|
{
|
|
get
|
|
{
|
|
return this.maxHeight;
|
|
}
|
|
set
|
|
{
|
|
this.maxHeight = value;
|
|
this.NotifyOfPropertyChange(() => this.MaxHeight);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets MinHeight.
|
|
/// </summary>
|
|
public int MaxWidth
|
|
{
|
|
get
|
|
{
|
|
return this.maxWidth;
|
|
}
|
|
set
|
|
{
|
|
this.maxWidth = value;
|
|
this.NotifyOfPropertyChange(() => this.MaxWidth);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether show keep ar.
|
|
/// </summary>
|
|
public bool ShowKeepAR
|
|
{
|
|
get
|
|
{
|
|
return this.showKeepAr;
|
|
}
|
|
set
|
|
{
|
|
this.showKeepAr = value;
|
|
this.NotifyOfPropertyChange(() => this.ShowKeepAR);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Task Properties
|
|
|
|
/// <summary>
|
|
/// Gets or sets CropBottom.
|
|
/// </summary>
|
|
public int CropBottom
|
|
{
|
|
get
|
|
{
|
|
return this.Task.Cropping.Bottom;
|
|
}
|
|
|
|
set
|
|
{
|
|
this.Task.Cropping.Bottom = value;
|
|
this.NotifyOfPropertyChange(() => this.CropBottom);
|
|
this.RecaulcatePictureSettingsProperties(ChangedPictureField.Crop);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets CropLeft.
|
|
/// </summary>
|
|
public int CropLeft
|
|
{
|
|
get
|
|
{
|
|
return this.Task.Cropping.Left;
|
|
}
|
|
|
|
set
|
|
{
|
|
this.Task.Cropping.Left = value;
|
|
this.NotifyOfPropertyChange(() => this.CropLeft);
|
|
this.RecaulcatePictureSettingsProperties(ChangedPictureField.Crop);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets CropRight.
|
|
/// </summary>
|
|
public int CropRight
|
|
{
|
|
get
|
|
{
|
|
return this.Task.Cropping.Right;
|
|
}
|
|
|
|
set
|
|
{
|
|
this.Task.Cropping.Right = value;
|
|
this.NotifyOfPropertyChange(() => this.CropRight);
|
|
this.RecaulcatePictureSettingsProperties(ChangedPictureField.Crop);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets CropTop.
|
|
/// </summary>
|
|
public int CropTop
|
|
{
|
|
get
|
|
{
|
|
return this.Task.Cropping.Top;
|
|
}
|
|
|
|
set
|
|
{
|
|
this.Task.Cropping.Top = value;
|
|
this.NotifyOfPropertyChange(() => this.CropTop);
|
|
this.RecaulcatePictureSettingsProperties(ChangedPictureField.Crop);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether IsCustomCrop.
|
|
/// </summary>
|
|
public bool IsCustomCrop
|
|
{
|
|
get
|
|
{
|
|
return this.Task.HasCropping;
|
|
}
|
|
|
|
set
|
|
{
|
|
this.Task.HasCropping = value;
|
|
this.NotifyOfPropertyChange(() => this.IsCustomCrop);
|
|
|
|
if (!value && this.currentTitle != null)
|
|
{
|
|
this.CropTop = currentTitle.AutoCropDimensions.Top;
|
|
this.CropBottom = currentTitle.AutoCropDimensions.Bottom;
|
|
this.CropLeft = currentTitle.AutoCropDimensions.Left;
|
|
this.CropRight = currentTitle.AutoCropDimensions.Right;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets DisplayWidth.
|
|
/// </summary>
|
|
public long DisplayWidth
|
|
{
|
|
get
|
|
{
|
|
return this.Task.DisplayWidth.HasValue
|
|
? int.Parse(Math.Round(this.Task.DisplayWidth.Value, 0).ToString(CultureInfo.InvariantCulture))
|
|
: 0;
|
|
}
|
|
|
|
set
|
|
{
|
|
if (!object.Equals(this.Task.DisplayWidth, value))
|
|
{
|
|
this.Task.DisplayWidth = value;
|
|
this.NotifyOfPropertyChange(() => this.DisplayWidth);
|
|
this.RecaulcatePictureSettingsProperties(ChangedPictureField.DisplayWidth);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets Width.
|
|
/// </summary>
|
|
public int Width
|
|
{
|
|
get
|
|
{
|
|
return this.Task.Width.HasValue ? this.Task.Width.Value : 0;
|
|
}
|
|
|
|
set
|
|
{
|
|
if (!object.Equals(this.Task.Width, value))
|
|
{
|
|
this.Task.Width = value;
|
|
this.NotifyOfPropertyChange(() => this.Width);
|
|
this.RecaulcatePictureSettingsProperties(ChangedPictureField.Width);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets Height.
|
|
/// </summary>
|
|
public int Height
|
|
{
|
|
get
|
|
{
|
|
return this.Task.Height.HasValue ? this.Task.Height.Value : 0;
|
|
}
|
|
|
|
set
|
|
{
|
|
if (!object.Equals(this.Task.Height, value))
|
|
{
|
|
this.Task.Height = value;
|
|
this.NotifyOfPropertyChange(() => this.Height);
|
|
this.RecaulcatePictureSettingsProperties(ChangedPictureField.Height);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether MaintainAspectRatio.
|
|
/// </summary>
|
|
public bool MaintainAspectRatio
|
|
{
|
|
get
|
|
{
|
|
return this.Task.KeepDisplayAspect;
|
|
}
|
|
|
|
set
|
|
{
|
|
this.Task.KeepDisplayAspect = value;
|
|
this.NotifyOfPropertyChange(() => this.MaintainAspectRatio);
|
|
this.RecaulcatePictureSettingsProperties(ChangedPictureField.MaintainAspectRatio);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets ParHeight.
|
|
/// </summary>
|
|
public int ParHeight
|
|
{
|
|
get
|
|
{
|
|
return this.Task.PixelAspectY;
|
|
}
|
|
|
|
set
|
|
{
|
|
if (!object.Equals(this.Task.PixelAspectY, value))
|
|
{
|
|
this.Task.PixelAspectY = value;
|
|
this.NotifyOfPropertyChange(() => this.ParHeight);
|
|
this.RecaulcatePictureSettingsProperties(ChangedPictureField.ParH);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets ParWidth.
|
|
/// </summary>
|
|
public int ParWidth
|
|
{
|
|
get
|
|
{
|
|
return this.Task.PixelAspectX;
|
|
}
|
|
|
|
set
|
|
{
|
|
if (!object.Equals(this.Task.PixelAspectX, value))
|
|
{
|
|
this.Task.PixelAspectX = value;
|
|
this.NotifyOfPropertyChange(() => this.ParWidth);
|
|
this.RecaulcatePictureSettingsProperties(ChangedPictureField.ParW);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets SelectedAnamorphicMode.
|
|
/// </summary>
|
|
public Anamorphic SelectedAnamorphicMode
|
|
{
|
|
get
|
|
{
|
|
return this.Task.Anamorphic;
|
|
}
|
|
|
|
set
|
|
{
|
|
if (!object.Equals(this.SelectedAnamorphicMode, value))
|
|
{
|
|
this.Task.Anamorphic = value;
|
|
this.NotifyOfPropertyChange(() => this.SelectedAnamorphicMode);
|
|
this.RecaulcatePictureSettingsProperties(ChangedPictureField.Anamorphic);
|
|
this.OnTabStatusChanged(null);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets SelectedModulus.
|
|
/// </summary>
|
|
public int? SelectedModulus
|
|
{
|
|
get
|
|
{
|
|
return this.Task.Modulus;
|
|
}
|
|
|
|
set
|
|
{
|
|
this.Task.Modulus = value;
|
|
this.NotifyOfPropertyChange(() => this.SelectedModulus);
|
|
this.RecaulcatePictureSettingsProperties(ChangedPictureField.Modulus);
|
|
this.OnTabStatusChanged(null);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Public Methods
|
|
|
|
/// <summary>
|
|
/// Setup this tab for the specified preset.
|
|
/// </summary>
|
|
/// <param name="preset">
|
|
/// The preset.
|
|
/// </param>
|
|
/// <param name="task">
|
|
/// The task.
|
|
/// </param>
|
|
public void SetPreset(Preset preset, EncodeTask task)
|
|
{
|
|
this.Task = task;
|
|
|
|
// Handle built-in presets.
|
|
if (preset.IsBuildIn)
|
|
{
|
|
preset.PictureSettingsMode = PresetPictureSettingsMode.Custom;
|
|
}
|
|
|
|
// Cropping
|
|
if (preset.Task.HasCropping)
|
|
{
|
|
this.IsCustomCrop = true;
|
|
this.Task.Cropping.Left = preset.Task.Cropping.Left;
|
|
this.Task.Cropping.Right = preset.Task.Cropping.Right;
|
|
this.Task.Cropping.Top = preset.Task.Cropping.Top;
|
|
this.Task.Cropping.Bottom = preset.Task.Cropping.Bottom;
|
|
|
|
this.NotifyOfPropertyChange(() => this.CropLeft);
|
|
this.NotifyOfPropertyChange(() => this.CropRight);
|
|
this.NotifyOfPropertyChange(() => this.CropTop);
|
|
this.NotifyOfPropertyChange(() => this.CropBottom);
|
|
}
|
|
else
|
|
{
|
|
this.IsCustomCrop = false;
|
|
}
|
|
|
|
|
|
// Setup the Picture Sizes
|
|
switch (preset.PictureSettingsMode)
|
|
{
|
|
default:
|
|
case PresetPictureSettingsMode.Custom:
|
|
case PresetPictureSettingsMode.SourceMaximum:
|
|
|
|
// Anamorphic Mode
|
|
this.SelectedAnamorphicMode = preset.Task.Anamorphic;
|
|
|
|
// Modulus
|
|
if (preset.Task.Modulus.HasValue)
|
|
{
|
|
this.SelectedModulus = preset.Task.Modulus;
|
|
}
|
|
|
|
// Set the Maintain Aspect ratio.
|
|
this.MaintainAspectRatio = preset.Task.KeepDisplayAspect;
|
|
|
|
// Set the Maximum so libhb can correctly manage the size.
|
|
if (preset.PictureSettingsMode == PresetPictureSettingsMode.SourceMaximum)
|
|
{
|
|
this.MaxWidth = this.sourceResolution.Width;
|
|
this.MaxHeight = this.sourceResolution.Height;
|
|
}
|
|
else
|
|
{
|
|
|
|
int presetWidth = preset.Task.MaxWidth ?? this.sourceResolution.Width;
|
|
int presetHeight = preset.Task.MaxHeight ?? this.sourceResolution.Height;
|
|
|
|
this.MaxWidth = presetWidth <= this.sourceResolution.Width ? presetWidth : this.sourceResolution.Width;
|
|
this.MaxHeight = presetHeight <= this.sourceResolution.Height ? presetHeight : this.sourceResolution.Height;
|
|
}
|
|
|
|
// Set the width, then check the height doesn't breach the max height and correct if necessary.
|
|
int width = this.GetModulusValue(this.GetRes((this.sourceResolution.Width - this.CropLeft - this.CropRight), this.MaxWidth));
|
|
int height = this.GetModulusValue(this.GetRes((this.sourceResolution.Height - this.CropTop - this.CropBottom), this.MaxHeight));
|
|
|
|
// Set the backing fields to avoid triggering recalulation until both are set.
|
|
this.Task.Width = width;
|
|
this.Task.Height = height;
|
|
|
|
// Trigger a Recalc
|
|
this.RecaulcatePictureSettingsProperties(ChangedPictureField.Width);
|
|
|
|
// Update the UI
|
|
this.NotifyOfPropertyChange(() => this.Width);
|
|
this.NotifyOfPropertyChange(() => this.Height);
|
|
|
|
break;
|
|
case PresetPictureSettingsMode.None:
|
|
// Do Nothing except reset the Max Width/Height
|
|
this.MaxWidth = this.sourceResolution.Width;
|
|
this.MaxHeight = this.sourceResolution.Height;
|
|
this.SelectedAnamorphicMode = preset.Task.Anamorphic;
|
|
|
|
if (this.Width > this.MaxWidth)
|
|
{
|
|
// Trigger a Recalc
|
|
this.Task.Width = this.GetModulusValue(this.GetRes((this.sourceResolution.Width - this.CropLeft - this.CropRight), this.MaxWidth));
|
|
this.RecaulcatePictureSettingsProperties(ChangedPictureField.Width);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
// Custom Anamorphic
|
|
if (preset.Task.Anamorphic == Anamorphic.Custom)
|
|
{
|
|
this.DisplayWidth = preset.Task.DisplayWidth != null ? int.Parse(preset.Task.DisplayWidth.ToString()) : 0;
|
|
this.ParWidth = preset.Task.PixelAspectX;
|
|
this.ParHeight = preset.Task.PixelAspectY;
|
|
}
|
|
|
|
this.NotifyOfPropertyChange(() => this.Task);
|
|
|
|
this.UpdateVisibileControls();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Update all the UI controls based on the encode task passed in.
|
|
/// </summary>
|
|
/// <param name="task">
|
|
/// The task.
|
|
/// </param>
|
|
public void UpdateTask(EncodeTask task)
|
|
{
|
|
this.Task = task;
|
|
this.NotifyOfPropertyChange(() => this.Width);
|
|
this.NotifyOfPropertyChange(() => this.Height);
|
|
this.NotifyOfPropertyChange(() => this.SelectedAnamorphicMode);
|
|
this.NotifyOfPropertyChange(() => this.SelectedModulus);
|
|
this.NotifyOfPropertyChange(() => this.CropTop);
|
|
this.NotifyOfPropertyChange(() => this.CropBottom);
|
|
this.NotifyOfPropertyChange(() => this.CropLeft);
|
|
this.NotifyOfPropertyChange(() => this.CropRight);
|
|
this.NotifyOfPropertyChange(() => this.IsCustomCrop);
|
|
this.NotifyOfPropertyChange(() => this.MaintainAspectRatio);
|
|
this.NotifyOfPropertyChange(() => this.DisplayWidth);
|
|
this.NotifyOfPropertyChange(() => this.ParWidth);
|
|
this.NotifyOfPropertyChange(() => this.ParHeight);
|
|
|
|
this.UpdateVisibileControls();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Setup this window for a new source
|
|
/// </summary>
|
|
/// <param name="source">
|
|
/// The source.
|
|
/// </param>
|
|
/// <param name="title">
|
|
/// The title.
|
|
/// </param>
|
|
/// <param name="preset">
|
|
/// The preset.
|
|
/// </param>
|
|
/// <param name="task">
|
|
/// The task.
|
|
/// </param>
|
|
public void SetSource(Source source, Title title, Preset preset, EncodeTask task)
|
|
{
|
|
this.currentTitle = title;
|
|
this.Task = task;
|
|
|
|
this.scannedSource = source;
|
|
|
|
if (title != null)
|
|
{
|
|
// Set cached info
|
|
this.sourceParValues = title.ParVal;
|
|
this.sourceResolution = title.Resolution;
|
|
|
|
// Update the cropping values, preffering those in the presets.
|
|
if (!preset.Task.HasCropping)
|
|
{
|
|
this.Task.Cropping.Top = title.AutoCropDimensions.Top;
|
|
this.Task.Cropping.Bottom = title.AutoCropDimensions.Bottom;
|
|
this.Task.Cropping.Left = title.AutoCropDimensions.Left;
|
|
this.Task.Cropping.Right = title.AutoCropDimensions.Right;
|
|
this.IsCustomCrop = false;
|
|
}
|
|
else
|
|
{
|
|
this.Task.Cropping.Left = preset.Task.Cropping.Left;
|
|
this.Task.Cropping.Right = preset.Task.Cropping.Right;
|
|
this.Task.Cropping.Top = preset.Task.Cropping.Top;
|
|
this.Task.Cropping.Bottom = preset.Task.Cropping.Bottom;
|
|
this.IsCustomCrop = true;
|
|
}
|
|
|
|
// Set the Max Width / Height available to the user controls.
|
|
// Preset Max is null for None / SourceMax
|
|
this.MaxWidth = preset.Task.MaxWidth ?? this.sourceResolution.Width;
|
|
if (this.sourceResolution.Width < this.MaxWidth)
|
|
{
|
|
this.MaxWidth = this.sourceResolution.Width;
|
|
}
|
|
|
|
this.MaxHeight = preset.Task.MaxHeight ?? this.sourceResolution.Height;
|
|
if (this.sourceResolution.Height < this.MaxHeight)
|
|
{
|
|
this.MaxHeight = this.sourceResolution.Height;
|
|
}
|
|
|
|
// Set the W/H
|
|
if (preset.PictureSettingsMode == PresetPictureSettingsMode.None)
|
|
{
|
|
this.Task.Width = this.GetModulusValue(this.sourceResolution.Width - this.CropLeft - this.CropRight);
|
|
this.Task.Height = this.GetModulusValue(this.sourceResolution.Height - this.CropTop - this.CropBottom);
|
|
}
|
|
else if (preset.PictureSettingsMode == PresetPictureSettingsMode.SourceMaximum)
|
|
{
|
|
this.Task.Width = this.GetModulusValue(this.sourceResolution.Width - this.CropLeft - this.CropRight);
|
|
this.Task.Height = this.GetModulusValue(this.sourceResolution.Height - this.CropTop - this.CropBottom);
|
|
this.MaintainAspectRatio = preset.Task.KeepDisplayAspect;
|
|
}
|
|
else
|
|
{
|
|
// Custom
|
|
// Set the Width, and Maintain Aspect ratio. That should calc the Height for us.
|
|
this.Task.Width = this.GetModulusValue(this.sourceResolution.Width - this.CropLeft - this.CropRight);
|
|
|
|
if (this.SelectedAnamorphicMode != Anamorphic.Loose)
|
|
{
|
|
this.Task.Height = this.GetModulusValue(this.sourceResolution.Height - this.CropTop - this.CropBottom);
|
|
}
|
|
}
|
|
|
|
// Set Screen Controls
|
|
this.SourceInfo = string.Format(
|
|
"{0}x{1}, PAR: {2}/{3}",
|
|
title.Resolution.Width,
|
|
title.Resolution.Height,
|
|
title.ParVal.Width,
|
|
title.ParVal.Height);
|
|
|
|
// Force a re-calc. This will handle MaxWidth / Height corrections.
|
|
this.RecaulcatePictureSettingsProperties(ChangedPictureField.Width);
|
|
}
|
|
|
|
this.NotifyOfPropertyChange(() => this.Task);
|
|
}
|
|
|
|
public bool MatchesPreset(Preset preset)
|
|
{
|
|
if (preset.Task.Anamorphic != this.SelectedAnamorphicMode)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (preset.Task.Modulus != this.SelectedModulus)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Methods
|
|
|
|
protected virtual void OnTabStatusChanged(TabStatusEventArgs e)
|
|
{
|
|
this.TabStatusChanged?.Invoke(this, e);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The init.
|
|
/// </summary>
|
|
private void Init()
|
|
{
|
|
this.Task.Modulus = 16;
|
|
this.Task.KeepDisplayAspect = true;
|
|
|
|
this.NotifyOfPropertyChange(() => this.SelectedModulus);
|
|
this.NotifyOfPropertyChange(() => this.MaintainAspectRatio);
|
|
|
|
// Default the Max Width / Height to 1080p format
|
|
this.MaxHeight = 1080;
|
|
this.MaxWidth = 1920;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The get picture title info.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// The <see cref="PictureSize.PictureSettingsTitle"/>.
|
|
/// </returns>
|
|
private PictureSize.PictureSettingsTitle GetPictureTitleInfo()
|
|
{
|
|
PictureSize.PictureSettingsTitle title = new PictureSize.PictureSettingsTitle
|
|
{
|
|
Width = this.sourceResolution.Width,
|
|
Height = this.sourceResolution.Height,
|
|
ParW = this.sourceParValues.Width,
|
|
ParH = this.sourceParValues.Height,
|
|
};
|
|
|
|
return title;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The get picture settings.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// The <see cref="PictureSize.PictureSettingsJob"/>.
|
|
/// </returns>
|
|
private PictureSize.PictureSettingsJob GetPictureSettings(ChangedPictureField changedField)
|
|
{
|
|
PictureSize.PictureSettingsJob job = new PictureSize.PictureSettingsJob
|
|
{
|
|
Width = this.Width,
|
|
Height = this.Height,
|
|
ItuPar = false,
|
|
Modulus = this.SelectedModulus,
|
|
ParW = this.SelectedAnamorphicMode == Anamorphic.None ? 1 : this.ParWidth,
|
|
ParH = this.SelectedAnamorphicMode == Anamorphic.None ? 1 : this.ParHeight,
|
|
MaxWidth = this.MaxWidth,
|
|
MaxHeight = this.MaxHeight,
|
|
KeepDisplayAspect = this.MaintainAspectRatio,
|
|
AnamorphicMode = this.SelectedAnamorphicMode,
|
|
Crop = new Cropping(this.CropTop, this.CropBottom, this.CropLeft, this.CropRight),
|
|
};
|
|
|
|
if (this.SelectedAnamorphicMode == Anamorphic.Custom)
|
|
{
|
|
if (changedField == ChangedPictureField.DisplayWidth)
|
|
{
|
|
var displayWidth = this.DisplayWidth;
|
|
job.ParW = (int)displayWidth; // num
|
|
job.ParH = job.Width; // den
|
|
}
|
|
}
|
|
|
|
// Reduce the Par W/H if we can. Don't do it while the user is altering the PAR controls through as it will mess with the result.
|
|
if (changedField != ChangedPictureField.ParH && changedField != ChangedPictureField.ParW)
|
|
{
|
|
long x, y;
|
|
HandBrakeUtils.Reduce(job.ParW, job.ParH, out x, out y);
|
|
job.ParW = (int)y;
|
|
job.ParH = (int)x;
|
|
}
|
|
|
|
return job;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Recalculate the picture settings when the user changes a particular field defined in the ChangedPictureField enum.
|
|
/// The properties in this class are dumb. They simply call this method if there is a change.
|
|
/// It is the job of this method to update all affected private fields and raise change notifications.
|
|
/// </summary>
|
|
/// <param name="changedField">
|
|
/// The changed field.
|
|
/// </param>
|
|
private void RecaulcatePictureSettingsProperties(ChangedPictureField changedField)
|
|
{
|
|
// Sanity Check
|
|
if (this.currentTitle == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Step 1, Update what controls are visible.
|
|
this.UpdateVisibileControls();
|
|
|
|
// Step 2, Set sensible defaults
|
|
if (changedField == ChangedPictureField.Anamorphic && (this.SelectedAnamorphicMode == Anamorphic.None || this.SelectedAnamorphicMode == Anamorphic.Loose))
|
|
{
|
|
this.Task.Width = this.sourceResolution.Width > this.MaxWidth
|
|
? this.MaxWidth
|
|
: this.sourceResolution.Width;
|
|
this.Task.KeepDisplayAspect = true;
|
|
}
|
|
|
|
// Choose which setting to keep.
|
|
PictureSize.KeepSetting setting = PictureSize.KeepSetting.HB_KEEP_WIDTH;
|
|
switch (changedField)
|
|
{
|
|
case ChangedPictureField.Width:
|
|
setting = PictureSize.KeepSetting.HB_KEEP_WIDTH;
|
|
break;
|
|
case ChangedPictureField.Height:
|
|
setting = PictureSize.KeepSetting.HB_KEEP_HEIGHT;
|
|
break;
|
|
}
|
|
|
|
// Step 2, For the changed field, call hb_set_anamorphic_size and process the results.
|
|
PictureSize.AnamorphicResult result = PictureSize.hb_set_anamorphic_size2(this.GetPictureSettings(changedField), this.GetPictureTitleInfo(), setting);
|
|
double dispWidth = Math.Round((result.OutputWidth * result.OutputParWidth / result.OutputParHeight), 0);
|
|
|
|
this.Task.Width = result.OutputWidth;
|
|
this.Task.Height = result.OutputHeight;
|
|
long x, y;
|
|
HandBrakeUtils.Reduce((int)Math.Round(result.OutputParWidth, 0), (int)Math.Round(result.OutputParHeight, 0), out x, out y);
|
|
this.Task.PixelAspectX = (int)Math.Round(result.OutputParWidth, 0);
|
|
this.Task.PixelAspectY = (int)Math.Round(result.OutputParHeight, 0);
|
|
this.Task.DisplayWidth = dispWidth;
|
|
|
|
// Step 3, Set the display width label to indicate the output.
|
|
this.DisplaySize = this.sourceResolution == null || this.sourceResolution.IsEmpty
|
|
? string.Empty
|
|
: string.Format(Resources.PictureSettingsViewModel_StorageDisplayLabel, dispWidth, result.OutputHeight, this.ParWidth, this.ParHeight);
|
|
|
|
// Step 4, Force an update on all the UI elements.
|
|
this.NotifyOfPropertyChange(() => this.Width);
|
|
this.NotifyOfPropertyChange(() => this.Height);
|
|
this.NotifyOfPropertyChange(() => this.DisplayWidth);
|
|
this.NotifyOfPropertyChange(() => this.ParWidth);
|
|
this.NotifyOfPropertyChange(() => this.ParHeight);
|
|
this.NotifyOfPropertyChange(() => this.CropTop);
|
|
this.NotifyOfPropertyChange(() => this.CropBottom);
|
|
this.NotifyOfPropertyChange(() => this.CropLeft);
|
|
this.NotifyOfPropertyChange(() => this.CropRight);
|
|
this.NotifyOfPropertyChange(() => this.SelectedModulus);
|
|
this.NotifyOfPropertyChange(() => this.MaintainAspectRatio);
|
|
|
|
// Step 5, Update the Preview
|
|
if (delayedPreviewprocessor != null && this.Task != null && this.StaticPreviewViewModel != null && this.StaticPreviewViewModel.IsOpen)
|
|
{
|
|
delayedPreviewprocessor.PerformTask(() => this.StaticPreviewViewModel.UpdatePreviewFrame(this.Task, this.scannedSource), 800);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The update visible controls.
|
|
/// </summary>
|
|
private void UpdateVisibileControls()
|
|
{
|
|
this.ShowDisplaySize = true;
|
|
this.ShowKeepAR = true;
|
|
|
|
switch (this.SelectedAnamorphicMode)
|
|
{
|
|
case Anamorphic.None:
|
|
this.WidthControlEnabled = true;
|
|
this.HeightControlEnabled = true;
|
|
this.ShowCustomAnamorphicControls = false;
|
|
this.ShowModulus = true;
|
|
this.ShowDisplaySize = true;
|
|
this.ShowKeepAR = true;
|
|
break;
|
|
case Anamorphic.Automatic:
|
|
this.WidthControlEnabled = true;
|
|
this.HeightControlEnabled = true;
|
|
this.ShowCustomAnamorphicControls = false;
|
|
this.ShowModulus = true;
|
|
this.ShowKeepAR = false;
|
|
break;
|
|
|
|
case Anamorphic.Loose:
|
|
this.WidthControlEnabled = true;
|
|
this.HeightControlEnabled = false;
|
|
this.ShowCustomAnamorphicControls = false;
|
|
this.ShowModulus = true;
|
|
this.ShowKeepAR = false;
|
|
break;
|
|
|
|
case Anamorphic.Custom:
|
|
this.WidthControlEnabled = true;
|
|
this.HeightControlEnabled = true;
|
|
this.ShowCustomAnamorphicControls = true;
|
|
this.ShowModulus = true;
|
|
this.ShowDisplaySize = true;
|
|
this.ShowKeepAR = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// For a given value, correct so that it matches the users currently selected modulus value
|
|
/// </summary>
|
|
/// <param name="value">
|
|
/// The value.
|
|
/// </param>
|
|
/// <returns>
|
|
/// Value corrected so that value % selected modulus == 0
|
|
/// </returns>
|
|
private int GetModulusValue(double value)
|
|
{
|
|
if (this.SelectedModulus == null)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
double remainder = value % this.SelectedModulus.Value;
|
|
|
|
if (remainder.Equals(0.0d))
|
|
{
|
|
return (int)Math.Abs(value);
|
|
}
|
|
|
|
double result = remainder >= ((double)this.SelectedModulus.Value / 2)
|
|
? value + (this.SelectedModulus.Value - remainder)
|
|
: value - remainder;
|
|
|
|
return (int)Math.Abs(result);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The get res.
|
|
/// </summary>
|
|
/// <param name="value">
|
|
/// The value.
|
|
/// </param>
|
|
/// <param name="max">
|
|
/// The max.
|
|
/// </param>
|
|
/// <returns>
|
|
/// The <see cref="int"/>.
|
|
/// </returns>
|
|
private int GetRes(int value, int? max)
|
|
{
|
|
return max.HasValue ? (value > max.Value ? max.Value : value) : value;
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
/// <summary>
|
|
/// The changed picture field.
|
|
/// </summary>
|
|
public enum ChangedPictureField
|
|
{
|
|
Width,
|
|
Height,
|
|
ParW,
|
|
ParH,
|
|
DisplayWidth,
|
|
Crop,
|
|
Anamorphic,
|
|
MaintainAspectRatio,
|
|
Modulus
|
|
}
|
|
} |