using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using butterflow_ui.Properties;
using csmic;
namespace butterflow_ui
{
/// The butterflow options configuration. Contians all the options necessary to run butterflow and process a video.
public class OptionsConfiguration : PropertyChangedAlerter
{
#region Members
/// The default pyramid scale setting.
private const decimal DEFAULT_PYRAMID_SCALE = 0.5m;
/// The default levels setting.
private const int DEFAULT_LEVELS = 3;
/// The default window size setting.
private const int DEFAULT_WINDOW_SIZE = 25;
/// The default iterations setting.
private const int DEFAULT_ITERATIONS = 3;
/// The default pixel neighborhood setting.
private const int DEFAULT_PIXEL_NEIGHBORHOOD = 5;
/// The default smooth derivative standard deviation setting.
private const decimal DEFAULT_SMOOTH_DERIVATIVE_STANDARD_DEVIATION = 1.1m;
/// The default flow filter type setting.
private const FlowFilterType DEFAULT_FLOW_FILTER_TYPE = FlowFilterType.box;
/// The output file format when operating on more than one video.
private const string OUTPUT_FILE_FORMAT = "{0}_{1}";
/// An input interpreter used for converting string values to numeric values.
[NonSerialized]
private InputInterpreter interpreter = new InputInterpreter();
/// The aspect ratio used for calculating heights when the aspect ratio is locked.
private decimal aspectRatio = 0;
/// The playback rate.
private string playbackRate;
/// A value indicating whether or not to keep the original audio in the final video.
private bool keepAudio;
/// The width of the output video.
private int width;
/// The height of the output video.
private int height;
/// A value indicating whether or not to render unspecified subregions.
private bool keepSubregions;
/// A value indicating whether or not to render the final video with lossless quality.
private bool losslessQuality;
/// A value indicating whether or not to tune processing for smooth motion.
private bool smoothMotion;
/// A value indicating whether or not to lock the aspect ratio to the of the video.
private bool lockAspectRatio;
/// The video input files.
private IEnumerable videoInput;
/// The video output file.
private string videoOutput;
/// A value indicating whether or not to use fast pyramids when processing a video.
private bool fastPyramid;
/// The pyramid scale setting.
private decimal pyramidScale;
/// The level size setting.
private int levels;
/// Size of the windowing average.
private int windowSize;
/// The number of iterations per pyramid level.
private int iterations;
/// The size of pixel neighborhood.
private int pixelNeighborhood;
/// The standard deviation of smooth derivatives
private decimal smoothDerivativeStandardDeviation;
/// Type of the flow filter to use for processing.
private FlowFilterType flowFilterType = FlowFilterType.box;
/// The subregions of the video on which to process.
private ObservableCollection subregions = new ObservableCollection();
#endregion Members
#region Properties
/// Gets the command line output given the current configuration.
/// The command line output.
public string CommandLineOutput
{
get
{
return ToButterflowArguments();
}
}
/// Gets a value indicating whether butterflow will process multiple files.
/// True if butterflow will process multiple files, false if not.
public bool MultipleFiles
{
get
{
return this.VideoInput != null && this.VideoInput.Count() > 1;
}
}
/// Gets or sets the playback rate.
/// The playback rate.
public string PlaybackRate
{
get
{
return this.playbackRate;
}
set
{
this.playbackRate = value;
OnPropertyChanged();
}
}
/// Gets or sets a value indicating whether the keep audio.
/// True if keeping audio in the final video, false if not.
public bool KeepAudio
{
get
{
return this.keepAudio;
}
set
{
this.keepAudio = value;
OnPropertyChanged();
}
}
/// Gets or sets a value indicating whether the butterflow should be turned toward smooth motion.
/// True if tuned toward smooth motion, false if not.
public bool SmoothMotion
{
get
{
return this.smoothMotion;
}
set
{
this.smoothMotion = value;
OnPropertyChanged();
}
}
/// Gets or sets a value indicating whether to lock aspect ratio of the video.
/// True if locking aspect ratio of the video, false if not.
public bool LockAspectRatio
{
get
{
return this.lockAspectRatio;
}
set
{
if(value && this.width != 0 && this.height != 0)
{
this.aspectRatio = Convert.ToDecimal(this.height) / Convert.ToDecimal(this.width);
}
this.lockAspectRatio = value;
OnPropertyChanged();
}
}
/// Gets or sets the width of the video output.
/// The width of the video output.
public string Width
{
get
{
return this.width.ToString();
}
set
{
var oldWidth = this.width;
interpreter.Interpret(value);
this.width = interpreter.Int;
OnPropertyChanged();
if(this.lockAspectRatio)
{
interpreter.Interpret(string.Format("{0} * {1}", this.aspectRatio, this.width));
this.height = interpreter.Int;
OnPropertyChanged("Height");
}
}
}
/// Gets or sets the height of the video output.
/// The height of the video output.
public string Height
{
get
{
return this.height.ToString();
}
set
{
interpreter.Interpret(value);
this.height = interpreter.Int;
OnPropertyChanged();
}
}
/// Gets or sets a value indicating whether the keep subregions that are not explicitly specified.
/// True if keeping subregions not explicitly specified, false if not.
public bool KeepSubregions
{
get
{
return this.keepSubregions;
}
set
{
this.keepSubregions = value;
OnPropertyChanged();
}
}
/// Gets or sets a value indicating whether the result is rendered in lossless quality.
/// True if lossless quality is selected, false if not.
public bool LosslessQuality
{
get
{
return this.losslessQuality;
}
set
{
this.losslessQuality = value;
OnPropertyChanged();
}
}
/// Gets or sets the video input file path.
/// The video input file path.
public IEnumerable VideoInput
{
get
{
return this.videoInput;
}
set
{
this.videoInput = value;
OnPropertyChanged();
OnPropertyChanged("MultipleFiles");
}
}
/// Gets or sets the video output file path.
/// The video output file path.
public string VideoOutput
{
get
{
return this.videoOutput;
}
set
{
this.videoOutput = value;
OnPropertyChanged();
}
}
/// Gets or sets a value indicating whether to use fast pyramids.
/// True if using fast pyramids, false if not.
public bool FastPyramid
{
get
{
return this.fastPyramid;
}
set
{
this.fastPyramid = value;
OnPropertyChanged();
}
}
/// Gets or sets the pyramid scale factor.
/// The pyramid scale factor.
public string PyramidScale
{
get
{
return this.pyramidScale.ToString();
}
set
{
interpreter.Interpret(value);
this.pyramidScale = interpreter.Decimal;
OnPropertyChanged();
}
}
/// Gets or sets the number of pyramid layers.
/// The number of pyramid layers.
public string Levels
{
get
{
return this.levels.ToString();
}
set
{
interpreter.Interpret(value);
this.levels = interpreter.Int;
OnPropertyChanged();
}
}
/// Gets or sets the size of the windowing average.
/// The size of the windowing average.
public string WindowSize
{
get
{
return this.windowSize.ToString();
}
set
{
interpreter.Interpret(value);
this.windowSize = interpreter.Int;
OnPropertyChanged();
}
}
/// Gets or sets the number of iterations at each pyramid level.
/// The number of iterations at each pyramid level.
public string Iterations
{
get
{
return this.iterations.ToString();
}
set
{
interpreter.Interpret(value);
this.iterations = interpreter.Int;
OnPropertyChanged();
}
}
/// Gets or sets the size of the pixel neighborhood.
/// The size of the pixel neighborhood.
/// Per butterflow's documentation, the valid range for --poly-n is {5,7}.
public string PixelNeighborhood
{
get
{
return this.pixelNeighborhood.ToString();
}
set
{
interpreter.Interpret(value);
if(interpreter.Int >= 5 && interpreter.Int <= 7)
{
this.pixelNeighborhood = interpreter.Int;
}
OnPropertyChanged();
}
}
/// Gets or sets the standard deviation of smooth derivatives.
/// The standard deviation of smooth derivatives.
public string SmoothDerivativeStandardDeviation
{
get
{
return this.smoothDerivativeStandardDeviation.ToString();
}
set
{
interpreter.Interpret(value);
this.smoothDerivativeStandardDeviation = interpreter.Decimal;
OnPropertyChanged();
}
}
/// Gets or sets the type of the flow filter used for optical flow calculations.
/// The type of the flow filter used for optical flow calculations.
public FlowFilterType FlowFilterType
{
get
{
return this.flowFilterType;
}
set
{
this.flowFilterType = value;
OnPropertyChanged();
}
}
/// Gets or sets the subregions of the video on which to work.
/// The subregions of the video.
public ObservableCollection Subregions
{
get
{
return this.subregions;
}
set
{
this.subregions = value;
OnPropertyChanged();
}
}
#endregion Properties
#region Contructors
/// Default constructor.
public OptionsConfiguration()
{
// Set default values
this.pyramidScale = DEFAULT_PYRAMID_SCALE;
this.levels = DEFAULT_LEVELS;
this.windowSize = DEFAULT_WINDOW_SIZE;
this.iterations = DEFAULT_ITERATIONS;
this.pixelNeighborhood = DEFAULT_PIXEL_NEIGHBORHOOD;
this.smoothDerivativeStandardDeviation = DEFAULT_SMOOTH_DERIVATIVE_STANDARD_DEVIATION;
this.flowFilterType = DEFAULT_FLOW_FILTER_TYPE;
this.videoInput = new string[0];
this.subregions.CollectionChanged += Subregions_CollectionChanged; ;
}
/// Event handler. Called by Subregions for collection changed events.
/// Source of the event.
/// Notify collection changed event information.
private void Subregions_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if(e.NewItems != null)
{
foreach(ButterflowSubregion newItem in e.NewItems)
{
newItem.PropertyChanged += SubregionPropertyChanged;
}
}
if(e.OldItems != null)
{
foreach(ButterflowSubregion oldItem in e.OldItems)
{
oldItem.PropertyChanged -= SubregionPropertyChanged;
}
}
OnPropertyChanged("CommandLineOutput");
}
#endregion Contructors
#region Methods
/// Subregion property changed.
/// Source of the event.
/// Property changed event information.
private void SubregionPropertyChanged(object sender, PropertyChangedEventArgs e)
{
OnPropertyChanged("CommandLineOutput");
}
/// Force the object's properties to report as updated.
public void ForceUpdate()
{
OnPropertyChanged();
}
/// Converts this object to a .
/// This object as an OptionsConfigurationFile.
public OptionsConfigurationFile ToFile()
{
var file = new OptionsConfigurationFile()
{
FastPyramid = this.fastPyramid,
FlowFilterType = this.flowFilterType,
Iterations = this.iterations,
KeepAudio = this.keepAudio,
KeepSubregions = this.keepSubregions,
Levels = this.levels,
LockAspectRatio = this.lockAspectRatio,
LosslessQuality = this.losslessQuality,
PixelNeighborhood = this.pixelNeighborhood,
PlaybackRate = this.playbackRate,
PyramidScale = this.pyramidScale,
SmoothDerivativeStandardDeviation = this.smoothDerivativeStandardDeviation,
SmoothMotion = this.smoothMotion,
WindowSize = this.windowSize
};
return file;
}
/// Loads an option configuration file's contents into the .
/// The file to load.
public void LoadFile(OptionsConfigurationFile file)
{
this.FastPyramid = file.FastPyramid;
this.FlowFilterType = file.FlowFilterType;
this.Iterations = file.Iterations.ToString();
this.KeepAudio = file.KeepAudio;
this.KeepSubregions = file.KeepSubregions;
this.Levels = file.Levels.ToString();
this.LockAspectRatio = file.LockAspectRatio;
this.LosslessQuality = file.LosslessQuality;
this.PixelNeighborhood = file.PixelNeighborhood.ToString();
this.PlaybackRate = file.PlaybackRate;
this.PyramidScale = file.PyramidScale.ToString();
this.SmoothDerivativeStandardDeviation = file.SmoothDerivativeStandardDeviation.ToString();
this.SmoothMotion = file.SmoothMotion;
this.WindowSize = file.WindowSize.ToString();
}
/// Converts this object to a butterflow options.
/// This object as a string.
public string ToButterflowArguments(int videoInputIndex = 0)
{
var stringBuilder = new StringBuilder("-v "); // Verbose
if(this.LockAspectRatio)
{
stringBuilder.AppendFormat("-vs {0}:-1 ", this.Width);
}
else
{
stringBuilder.AppendFormat("-vs {0}:{1} ", this.Width, this.Height);
}
if(!Settings.Default.UseDefaultDevice)
{
if(Settings.Default.Device == 0)
{
stringBuilder.Append("-sw ");
}
if(Settings.Default.Device > 0)
{
stringBuilder.AppendFormat("-device {0} ", Settings.Default.Device - 1);
}
}
if(!string.IsNullOrWhiteSpace(this.PlaybackRate)) stringBuilder.AppendFormat("-r {0} ", this.PlaybackRate);
if(this.KeepAudio) stringBuilder.Append("-audio ");
if(this.LosslessQuality) stringBuilder.Append("-l ");
if(this.Subregions.Any())
{
stringBuilder.Append("-s ");
foreach(var anon in this.Subregions.Select((sr, index) => new { Index = index, Subregion = sr }))
{
string format = "ss\\.fff";
if(anon.Index > 0)
{
stringBuilder.Append(":");
}
if(anon.Subregion.Start.TotalHours > 1)
{
format = "h\\:m\\:s\\.fff";
}
else if(anon.Subregion.Start.TotalMinutes > 1)
{
format = "m\\:s\\.fff";
}
stringBuilder.AppendFormat("a={0},b={1},{2}={3}", anon.Subregion.Start.ToString(format), anon.Subregion.ToEnd ? "end" : anon.Subregion.End.ToString(format), anon.Subregion.SubregionType, anon.Subregion.Value);
}
stringBuilder.Append(" ");
}
if(this.KeepSubregions) stringBuilder.Append("-k ");
if(this.SmoothMotion) stringBuilder.Append("-sm ");
if(this.FastPyramid) stringBuilder.Append("--fast-pyr ");
if(this.pyramidScale != DEFAULT_PYRAMID_SCALE) stringBuilder.AppendFormat("--pyr-scale {0} ", this.PyramidScale);
if(this.levels != DEFAULT_LEVELS) stringBuilder.AppendFormat("--levels {0} ", this.Levels);
if(this.windowSize != DEFAULT_WINDOW_SIZE) stringBuilder.AppendFormat("--winsize {0} ", this.WindowSize);
if(this.iterations != DEFAULT_ITERATIONS) stringBuilder.AppendFormat("--iters {0} ", this.Iterations);
if(this.pixelNeighborhood != DEFAULT_PIXEL_NEIGHBORHOOD) stringBuilder.AppendFormat("--poly-n {0} ", this.PixelNeighborhood);
if(this.smoothDerivativeStandardDeviation != DEFAULT_SMOOTH_DERIVATIVE_STANDARD_DEVIATION) stringBuilder.AppendFormat("--poly-s {0} ", this.SmoothDerivativeStandardDeviation);
if(this.FlowFilterType != DEFAULT_FLOW_FILTER_TYPE) stringBuilder.AppendFormat("-ff {0} ", this.FlowFilterType);
if(!string.IsNullOrWhiteSpace(this.VideoOutput))
{
string videoOutputFile = string.Empty;
if(this.MultipleFiles)
{
var format = new StringBuilder(Path.GetFileNameWithoutExtension(this.VideoOutput));
format.Append("_{0:");
for(int i = 0; i < this.videoInput.Count().ToString().Length; i++)
{
format.Append("0");
}
format.Append("}");
format.Append(Path.GetExtension(this.VideoOutput));
var newName = string.Format(format.ToString(), videoInputIndex + 1); // Since the index is zero based, we will add one to make the output more human readable.
videoOutputFile = Path.Combine(Path.GetDirectoryName(this.VideoOutput), newName);
}
else
{
videoOutputFile = this.VideoOutput;
}
stringBuilder.AppendFormat("-o \"{0}\" ", videoOutputFile);
}
if(this.VideoInput.Any())
{
stringBuilder.AppendFormat("\"{0}\"", this.VideoInput.ElementAt(videoInputIndex));
}
return stringBuilder.ToString();
}
/// Returns a string that represents the current object.
/// A string that represents the current object.
public override string ToString()
{
return ToButterflowArguments();
}
#endregion Methods
}
}