// --------------------------------------------------------------------------------------------------------------------
//
// This file is part of the HandBrake source code - It may be used under the terms of the GNU General Public License.
//
//
// The X264 Advanced View Model
//
// --------------------------------------------------------------------------------------------------------------------
namespace HandBrakeWPF.ViewModels
{
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using HandBrake.Interop.Interop.Model.Encoding;
using HandBrakeWPF.EventArgs;
using HandBrakeWPF.Helpers;
using HandBrakeWPF.Model;
using HandBrakeWPF.Services.Presets.Model;
using HandBrakeWPF.Services.Scan.Model;
using HandBrakeWPF.ViewModels.Interfaces;
using EncodeTask = HandBrakeWPF.Services.Encode.Model.EncodeTask;
///
/// The Advanced View Model
///
public class X264ViewModel : ViewModelBase, IX264ViewModel
{
#region Constants and Fields
///
/// The adaptive b frames.
///
private AdvancedChoice adaptiveBFrames;
///
/// The adaptive quantization strength.
///
private double adaptiveQuantizationStrength;
///
/// The analysis.
///
private AdvancedChoice analysis;
///
/// The b frames.
///
private AdvancedChoice bFrames;
///
/// The cabac entropy coding.
///
private bool cabacEntropyCoding;
///
/// The deblocking strength.
///
private AdvancedChoice deblockingStrength;
///
/// The deblocking threshold.
///
private AdvancedChoice deblockingThreshold;
///
/// The direct prediction.
///
private AdvancedChoice directPrediction;
///
/// The eight by eight dct.
///
private bool eightByEightDct;
///
/// The motion estimation method.
///
private AdvancedChoice motionEstimationMethod;
///
/// The motion estimation range.
///
private int motionEstimationRange;
///
/// The no dct decimate.
///
private bool noDctDecimate;
///
/// The psychovisual rate distortion.
///
private double psychovisualRateDistortion;
///
/// The psychovisual trellis.
///
private double psychovisualTrellis;
///
/// The pyramidal b frames.
///
private AdvancedChoice pyramidalBFrames;
///
/// The reference frames.
///
private AdvancedChoice referenceFrames;
///
/// The subpixel motion estimation.
///
private AdvancedChoice subpixelMotionEstimation;
///
/// The trellis.
///
private AdvancedChoice trellis;
///
/// X264 options that have UI elements that correspond to them.
///
private HashSet uiOptions = new HashSet
{
"ref",
"bframes",
"b-adapt",
"direct",
"weightp",
"b-pyramid",
"me",
"subme",
"subq",
"merange",
"analyse",
"8x8dct",
"cabac",
"trellis",
"aq-strength",
"psy-rd",
"no-dct-decimate",
"deblock"
};
///
/// The weighted p frames.
///
private bool weightedPFrames;
#endregion
#region Constructors and Destructors
///
/// Initializes a new instance of the class.
///
public X264ViewModel()
{
this.Task = new EncodeTask();
this.UpdateUIFromAdvancedOptions();
}
///
/// The task object property changed.
///
///
/// The sender.
///
///
/// The PropertyChangedEventArgs.
///
private void Task_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == UserSettingConstants.ShowAdvancedTab)
{
this.NotifyOfPropertyChange(() => this.AdvancedOptionsString);
if (this.Task.ShowAdvancedTab)
{
this.UpdateUIFromAdvancedOptions();
}
}
}
#endregion
public event EventHandler TabStatusChanged;
#region Properties
///
/// Gets or sets AdaptiveBFrames.
///
public AdvancedChoice AdaptiveBFrames
{
get
{
return this.adaptiveBFrames;
}
set
{
this.adaptiveBFrames = value;
this.NotifyOfPropertyChange(() => this.AdaptiveBFrames);
this.UpdateOptionsString();
}
}
///
/// Gets or sets AdaptiveQuantizationStrength.
///
public double AdaptiveQuantizationStrength
{
get
{
return this.adaptiveQuantizationStrength;
}
set
{
this.adaptiveQuantizationStrength = value;
this.NotifyOfPropertyChange(() => this.AdaptiveQuantizationStrength);
this.UpdateOptionsString();
}
}
///
/// Gets or sets AdvancedOptionsString.
///
public string AdvancedOptionsString
{
get
{
return this.Task.AdvancedEncoderOptions;
}
set
{
this.Task.AdvancedEncoderOptions = value;
this.UpdateUIFromAdvancedOptions();
this.NotifyOfPropertyChange(() => this.AdvancedOptionsString);
}
}
///
/// Gets or sets Analysis.
///
public AdvancedChoice Analysis
{
get
{
return this.analysis;
}
set
{
this.analysis = value;
this.NotifyOfPropertyChange(() => this.Analysis);
this.UpdateOptionsString();
}
}
///
/// Gets or sets a value indicating whether AutomaticChange.
///
public bool AutomaticChange { get; set; }
///
/// Gets or sets BFrames.
///
public AdvancedChoice BFrames
{
get
{
return this.bFrames;
}
set
{
this.bFrames = value;
this.NotifyOfPropertyChange(() => this.BFrames);
this.NotifyOfPropertyChange(() => this.BFramesOptionsVisible);
this.NotifyOfPropertyChange(() => this.PyramidalBFramesVisible);
this.UpdateOptionsString();
}
}
///
/// Gets a value indicating whether BFramesOptionsVisible.
///
public bool BFramesOptionsVisible
{
get
{
return this.BFrames.Value != "0";
}
}
///
/// Gets or sets a value indicating whether CabacEntropyCoding.
///
public bool CabacEntropyCoding
{
get
{
return this.cabacEntropyCoding;
}
set
{
this.cabacEntropyCoding = value;
this.NotifyOfPropertyChange(() => this.CabacEntropyCoding);
this.NotifyOfPropertyChange(() => this.PsychovisualTrellisVisible);
this.UpdateOptionsString();
}
}
///
/// Gets or sets DeblockingStrength.
///
public AdvancedChoice DeblockingStrength
{
get
{
return this.deblockingStrength;
}
set
{
this.deblockingStrength = value;
this.NotifyOfPropertyChange(() => this.DeblockingStrength);
this.UpdateOptionsString();
}
}
///
/// Gets or sets DeblockingThreshold.
///
public AdvancedChoice DeblockingThreshold
{
get
{
return this.deblockingThreshold;
}
set
{
this.deblockingThreshold = value;
this.NotifyOfPropertyChange(() => this.DeblockingThreshold);
this.UpdateOptionsString();
}
}
///
/// Gets or sets DirectPrediction.
///
public AdvancedChoice DirectPrediction
{
get
{
return this.directPrediction;
}
set
{
this.directPrediction = value;
this.NotifyOfPropertyChange(() => this.DirectPrediction);
this.UpdateOptionsString();
}
}
///
/// Gets or sets a value indicating whether EightByEightDct.
///
public bool EightByEightDct
{
get
{
return this.eightByEightDct;
}
set
{
this.eightByEightDct = value;
this.NotifyOfPropertyChange(() => this.EightByEightDct);
this.UpdateOptionsString();
}
}
///
/// Gets or sets MotionEstimationMethod.
///
public AdvancedChoice MotionEstimationMethod
{
get
{
return this.motionEstimationMethod;
}
set
{
this.motionEstimationMethod = value;
this.NotifyOfPropertyChange(() => this.MotionEstimationMethod);
if ((MotionEstimationMethod.Value == "hex" || MotionEstimationMethod.Value == "dia") && (motionEstimationRange > 16))
{
this.motionEstimationRange = 16;
this.NotifyOfPropertyChange(() => this.MotionEstimationRange);
}
this.UpdateOptionsString();
}
}
///
/// Gets or sets MotionEstimationRange.
///
public int MotionEstimationRange
{
get
{
return this.motionEstimationRange;
}
set
{
if ((MotionEstimationMethod.Value == "hex" || MotionEstimationMethod.Value == "dia") && (value > 16))
{
this.motionEstimationRange = 16;
}
else if (value < 4)
{
this.motionEstimationRange = 4;
}
else
{
this.motionEstimationRange = value;
}
this.NotifyOfPropertyChange(() => this.MotionEstimationRange);
this.UpdateOptionsString();
}
}
///
/// Gets or sets a value indicating whether NoDctDecimate.
///
public bool NoDctDecimate
{
get
{
return this.noDctDecimate;
}
set
{
this.noDctDecimate = value;
this.NotifyOfPropertyChange(() => this.NoDctDecimate);
this.UpdateOptionsString();
}
}
///
/// Gets or sets PsychovisualRateDistortion.
///
public double PsychovisualRateDistortion
{
get
{
return this.psychovisualRateDistortion;
}
set
{
this.psychovisualRateDistortion = value;
this.NotifyOfPropertyChange(() => this.PsychovisualRateDistortion);
this.UpdateOptionsString();
}
}
///
/// Gets or sets PsychovisualTrellis.
///
public double PsychovisualTrellis
{
get
{
return this.psychovisualTrellis;
}
set
{
this.psychovisualTrellis = value;
this.NotifyOfPropertyChange(() => this.PsychovisualTrellis);
this.UpdateOptionsString();
}
}
///
/// Gets a value indicating whether PsychovisualTrellisVisible.
///
public bool PsychovisualTrellisVisible
{
get
{
return this.CabacEntropyCoding && this.Trellis.Value != "0";
}
}
///
/// Gets a value indicating whether PsychovisualRateDistortionVisible.
///
public bool PsychovisualRateDistortionVisible
{
get
{
int value;
int.TryParse(this.SubpixelMotionEstimation.Value.Trim(), out value);
return value >= 6;
}
}
///
/// Gets or sets PyramidalBFrames.
///
public AdvancedChoice PyramidalBFrames
{
get
{
return this.pyramidalBFrames;
}
set
{
this.pyramidalBFrames = value;
this.NotifyOfPropertyChange(() => this.PyramidalBFrames);
this.UpdateOptionsString();
}
}
///
/// Gets a value indicating whether PyramidalBFramesVisible.
///
public bool PyramidalBFramesVisible
{
get
{
return int.Parse(this.BFrames.Value) > 1;
}
}
///
/// Gets or sets ReferenceFrames.
///
public AdvancedChoice ReferenceFrames
{
get
{
return this.referenceFrames;
}
set
{
this.referenceFrames = value;
this.NotifyOfPropertyChange(() => this.ReferenceFrames);
this.UpdateOptionsString();
}
}
///
/// Gets or sets SubpixelMotionEstimation.
///
public AdvancedChoice SubpixelMotionEstimation
{
get
{
return this.subpixelMotionEstimation;
}
set
{
this.subpixelMotionEstimation = value;
this.NotifyOfPropertyChange(() => this.SubpixelMotionEstimation);
this.NotifyOfPropertyChange(() => this.PsychovisualRateDistortionVisible);
this.UpdateOptionsString();
}
}
///
/// Gets or sets Task.
///
public EncodeTask Task { get; set; }
///
/// Gets or sets Trellis.
///
public AdvancedChoice Trellis
{
get
{
return this.trellis;
}
set
{
this.trellis = value;
this.NotifyOfPropertyChange(() => this.Trellis);
this.NotifyOfPropertyChange(() => this.PsychovisualTrellisVisible);
this.UpdateOptionsString();
}
}
///
/// Gets or sets a value indicating whether WeightedPFrames.
///
public bool WeightedPFrames
{
get
{
return this.weightedPFrames;
}
set
{
this.weightedPFrames = value;
this.NotifyOfPropertyChange(() => this.WeightedPFrames);
this.UpdateOptionsString();
}
}
#endregion
#region Public Methods
///
/// The update ui from advanced options.
///
public void UpdateUIFromAdvancedOptions()
{
this.AutomaticChange = true;
// Reset UI to defaults, and re-apply options.
this.SetAdvancedToDefaults();
if (this.Task.AdvancedEncoderOptions == null)
{
this.AutomaticChange = false;
return;
}
// Check the updated options string. Update UI for any recognized options.
string[] newOptionsSegments = this.Task.AdvancedEncoderOptions.Split(':');
foreach (string newOptionsSegment in newOptionsSegments)
{
int equalsIndex = newOptionsSegment.IndexOf('=');
if (equalsIndex >= 0)
{
string optionName = newOptionsSegment.Substring(0, equalsIndex);
string optionValue = newOptionsSegment.Substring(equalsIndex + 1);
if (optionName != string.Empty && optionValue != string.Empty)
{
AdvancedChoice newChoice;
int parseInt;
double parseDouble;
string[] subParts;
switch (optionName)
{
case "ref":
if (int.TryParse(optionValue, out parseInt))
{
newChoice =
AdvancedChoicesHelper.ReferenceFrames.SingleOrDefault(
choice => choice.Value == parseInt.ToString(CultureInfo.InvariantCulture));
if (newChoice != null)
{
this.ReferenceFrames = newChoice;
}
}
break;
case "bframes":
if (int.TryParse(optionValue, out parseInt))
{
newChoice =
AdvancedChoicesHelper.BFrames.SingleOrDefault(
choice => choice.Value == parseInt.ToString(CultureInfo.InvariantCulture));
if (newChoice != null)
{
this.BFrames = newChoice;
}
}
break;
case "b-adapt":
newChoice =
AdvancedChoicesHelper.AdaptiveBFrames.SingleOrDefault(
choice => choice.Value == optionValue);
if (newChoice != null)
{
this.AdaptiveBFrames = newChoice;
}
break;
case "direct":
newChoice =
AdvancedChoicesHelper.DirectPrediction.SingleOrDefault(
choice => choice.Value == optionValue);
if (newChoice != null)
{
this.DirectPrediction = newChoice;
}
break;
case "weightp":
if (optionValue == "0")
{
this.WeightedPFrames = false;
}
else if (optionValue == "1")
{
this.WeightedPFrames = true;
}
break;
case "b-pyramid":
newChoice =
AdvancedChoicesHelper.PyramidalBFrames.SingleOrDefault(
choice => choice.Value == optionValue);
if (newChoice != null)
{
this.PyramidalBFrames = newChoice;
}
break;
case "me":
newChoice =
AdvancedChoicesHelper.MotionEstimationMethod.SingleOrDefault(
choice => choice.Value == optionValue);
if (newChoice != null)
{
this.MotionEstimationMethod = newChoice;
}
break;
case "subme":
case "subq":
if (int.TryParse(optionValue, out parseInt))
{
newChoice =
AdvancedChoicesHelper.SubpixelMotionEstimation.SingleOrDefault(
choice => choice.Value == parseInt.ToString(CultureInfo.InvariantCulture));
if (newChoice != null)
{
this.SubpixelMotionEstimation = newChoice;
}
}
break;
case "merange":
if (int.TryParse(optionValue, out parseInt))
{
this.MotionEstimationRange = parseInt;
}
break;
case "analyse":
newChoice =
AdvancedChoicesHelper.Analysis.SingleOrDefault(
choice => choice.Value == optionValue);
if (newChoice != null)
{
this.Analysis = newChoice;
}
break;
case "8x8dct":
if (optionValue == "0")
{
this.EightByEightDct = false;
}
else if (optionValue == "1")
{
this.EightByEightDct = true;
}
break;
case "cabac":
if (optionValue == "0")
{
this.CabacEntropyCoding = false;
}
else if (optionValue == "1")
{
this.CabacEntropyCoding = true;
}
break;
case "trellis":
if (int.TryParse(optionValue, out parseInt))
{
newChoice =
AdvancedChoicesHelper.Trellis.SingleOrDefault(
choice => choice.Value == parseInt.ToString(CultureInfo.InvariantCulture));
if (newChoice != null)
{
this.Trellis = newChoice;
}
}
break;
case "aq-strength":
if (double.TryParse(optionValue, NumberStyles.Any, CultureInfo.InvariantCulture, out parseDouble) && parseDouble >= 0.0 &&
parseDouble <= 2.0)
{
this.AdaptiveQuantizationStrength = Math.Round(parseDouble, 1);
}
break;
case "psy-rd":
subParts = optionValue.Split(',');
if (subParts.Length == 2)
{
double psyRD, psyTrellis;
if (double.TryParse(subParts[0], NumberStyles.Any, CultureInfo.InvariantCulture, out psyRD) &&
double.TryParse(subParts[1], NumberStyles.Any, CultureInfo.InvariantCulture, out psyTrellis))
{
if (psyRD >= 0.0 && psyRD <= 2.0 && psyTrellis >= 0.0 && psyTrellis <= 1.0)
{
this.PsychovisualRateDistortion = Math.Round(psyRD, 1);
this.PsychovisualTrellis = Math.Round(psyTrellis, 2);
}
}
}
break;
case "no-dct-decimate":
if (optionValue == "0")
{
this.NoDctDecimate = false;
}
else if (optionValue == "1")
{
this.NoDctDecimate = true;
}
break;
case "deblock":
subParts = optionValue.Split(',');
if (subParts.Length == 2)
{
int dbStrength, dbThreshold;
if (int.TryParse(subParts[0], out dbStrength) &&
int.TryParse(subParts[1], out dbThreshold))
{
newChoice =
AdvancedChoicesHelper.DeblockingStrength.SingleOrDefault(
choice => choice.Value == subParts[0]);
if (newChoice != null)
{
this.DeblockingStrength = newChoice;
}
newChoice =
AdvancedChoicesHelper.DeblockingThreshold.SingleOrDefault(
choice => choice.Value == subParts[1]);
if (newChoice != null)
{
this.DeblockingThreshold = newChoice;
}
}
}
break;
}
}
}
}
this.AutomaticChange = false;
}
#endregion
#region Implemented Interfaces
#region IAdvancedViewModel
///
/// The set encoder.
///
///
/// The encoder.
///
public void SetEncoder(VideoEncoder encoder)
{
}
///
/// The clear.
///
public void Clear()
{
this.AdvancedOptionsString = string.Empty;
}
#endregion
#region ITabInterface
///
/// Setup this tab for the specified preset.
///
///
/// The preset.
///
///
/// The task.
///
public void SetPreset(Preset preset, EncodeTask task)
{
this.Task.PropertyChanged -= this.Task_PropertyChanged;
this.Task = task;
this.Task.PropertyChanged += this.Task_PropertyChanged;
this.AdvancedOptionsString = preset.Task.AdvancedEncoderOptions;
}
///
/// Update all the UI controls based on the encode task passed in.
///
///
/// The task.
///
public void UpdateTask(EncodeTask task)
{
this.Task = task;
this.SetEncoder(task.VideoEncoder);
this.AdvancedOptionsString = task.AdvancedEncoderOptions;
}
public bool MatchesPreset(Preset preset)
{
return true;
}
///
/// Setup this window for a new source
///
///
/// The source.
///
///
/// The title.
///
///
/// The preset.
///
///
/// The task.
///
public void SetSource(Source source, Title title, Preset preset, EncodeTask task)
{
this.Task = task;
this.NotifyOfPropertyChange(() => this.AdvancedOptionsString);
}
#endregion
#endregion
#region Methods
protected virtual void OnTabStatusChanged(TabStatusEventArgs e)
{
this.TabStatusChanged?.Invoke(this, e);
}
///
/// The set advanced to defaults.
///
private void SetAdvancedToDefaults()
{
this.ReferenceFrames = AdvancedChoicesHelper.ReferenceFrames.SingleOrDefault(choice => choice.IsDefault);
this.BFrames = AdvancedChoicesHelper.BFrames.SingleOrDefault(choice => choice.IsDefault);
this.AdaptiveBFrames = AdvancedChoicesHelper.AdaptiveBFrames.SingleOrDefault(choice => choice.IsDefault);
this.DirectPrediction = AdvancedChoicesHelper.DirectPrediction.SingleOrDefault(choice => choice.IsDefault);
this.WeightedPFrames = true;
this.PyramidalBFrames = AdvancedChoicesHelper.PyramidalBFrames.SingleOrDefault(choice => choice.IsDefault);
this.MotionEstimationMethod =
AdvancedChoicesHelper.MotionEstimationMethod.SingleOrDefault(choice => choice.IsDefault);
this.SubpixelMotionEstimation =
AdvancedChoicesHelper.SubpixelMotionEstimation.SingleOrDefault(choice => choice.IsDefault);
this.MotionEstimationRange = 16;
this.Analysis = AdvancedChoicesHelper.Analysis.SingleOrDefault(choice => choice.IsDefault);
this.EightByEightDct = true;
this.CabacEntropyCoding = true;
this.Trellis = AdvancedChoicesHelper.Trellis.SingleOrDefault(choice => choice.IsDefault);
this.AdaptiveQuantizationStrength = 1.0;
this.PsychovisualRateDistortion = 1.0;
this.PsychovisualTrellis = 0.0;
this.DeblockingStrength =
AdvancedChoicesHelper.DeblockingStrength.SingleOrDefault(choice => choice.IsDefault);
this.DeblockingThreshold =
AdvancedChoicesHelper.DeblockingThreshold.SingleOrDefault(choice => choice.IsDefault);
this.NoDctDecimate = false;
}
///
/// Update the x264 options string from a UI change.
///
private void UpdateOptionsString()
{
if (this.AutomaticChange)
{
return;
}
List newOptions = new List();
// First add any parts of the options string that don't correspond to the UI
if (this.AdvancedOptionsString != null)
{
string[] existingSegments = this.AdvancedOptionsString.Split(':');
foreach (string existingSegment in existingSegments)
{
string optionName = existingSegment;
int equalsIndex = existingSegment.IndexOf('=');
if (equalsIndex >= 0)
{
optionName = existingSegment.Substring(0, existingSegment.IndexOf("=", System.StringComparison.Ordinal));
}
if (!this.uiOptions.Contains(optionName) && optionName != string.Empty)
{
newOptions.Add(existingSegment);
}
}
}
// Now add everything from the UI
if (!this.ReferenceFrames.IsDefault)
{
newOptions.Add("ref=" + this.ReferenceFrames.Value);
}
if (!this.BFrames.IsDefault)
{
newOptions.Add("bframes=" + this.BFrames.Value);
}
if (this.BFrames.Value != "0")
{
if (!this.AdaptiveBFrames.IsDefault)
{
newOptions.Add("b-adapt=" + this.AdaptiveBFrames.Value);
}
if (!this.DirectPrediction.IsDefault)
{
newOptions.Add("direct=" + this.DirectPrediction.Value);
}
if (this.BFrames.Value != "1" && !this.PyramidalBFrames.IsDefault)
{
newOptions.Add("b-pyramid=" + this.PyramidalBFrames.Value);
}
}
if (!this.WeightedPFrames)
{
newOptions.Add("weightp=0");
}
if (!this.MotionEstimationMethod.IsDefault)
{
newOptions.Add("me=" + this.MotionEstimationMethod.Value);
}
if (!this.SubpixelMotionEstimation.IsDefault)
{
newOptions.Add("subme=" + this.SubpixelMotionEstimation.Value);
}
if (this.MotionEstimationRange != 16)
{
newOptions.Add("merange=" + this.MotionEstimationRange);
}
if (!this.Analysis.IsDefault)
{
newOptions.Add("analyse=" + this.Analysis.Value);
}
if (this.Analysis.Value != "none" && !this.EightByEightDct)
{
newOptions.Add("8x8dct=0");
}
if (!this.CabacEntropyCoding)
{
newOptions.Add("cabac=0");
}
if (!this.Trellis.IsDefault)
{
newOptions.Add("trellis=" + this.Trellis.Value);
}
double psTrellis = 0.0;
if (this.CabacEntropyCoding && this.Trellis.Value != "0")
{
psTrellis = this.PsychovisualTrellis;
}
if (this.AdaptiveQuantizationStrength != 1.0)
{
newOptions.Add(
"aq-strength=" + this.AdaptiveQuantizationStrength.ToString("F1", CultureInfo.InvariantCulture));
}
if (this.PsychovisualRateDistortion != 1.0 || psTrellis > 0.0)
{
newOptions.Add(
"psy-rd=" + this.PsychovisualRateDistortion.ToString("F1", CultureInfo.InvariantCulture) + "," +
psTrellis.ToString("F2", CultureInfo.InvariantCulture));
}
if (this.NoDctDecimate)
{
newOptions.Add("no-dct-decimate=1");
}
if (!this.DeblockingStrength.IsDefault || !this.DeblockingThreshold.IsDefault)
{
newOptions.Add("deblock=" + this.DeblockingStrength.Value + "," + this.DeblockingThreshold.Value);
}
this.Task.AdvancedEncoderOptions = string.Join(":", newOptions);
this.NotifyOfPropertyChange(() => this.AdvancedOptionsString);
}
#endregion
}
}