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 } }