From d0b29f20fdce6d87c156f5531e0c9bddeac24424 Mon Sep 17 00:00:00 2001 From: Jordan Wages Date: Mon, 2 Jul 2018 23:31:16 -0500 Subject: [PATCH] Subregions auto update. ButterflowWrapper now calls butterflow.exe --- butterflow-ui/ButterflowSubregion.cs | 105 +++++++++++++++++ butterflow-ui/ButterflowWrapper.cs | 89 ++++++++++++++- butterflow-ui/Icons.xaml | 18 +++ .../Localization/Localization.Designer.cs | 90 +++++++++++++++ butterflow-ui/Localization/Localization.resx | 30 +++++ butterflow-ui/MainWindow.xaml | 41 ++++++- butterflow-ui/MainWindow.xaml.cs | 80 ++++++++++++- butterflow-ui/OptionsConfiguration.cs | 107 ++++++++++++++---- butterflow-ui/PropertyChangedAlerter.cs | 56 +++++++++ butterflow-ui/RegionType.cs | 20 ++++ butterflow-ui/butterflow-ui.csproj | 3 + 11 files changed, 604 insertions(+), 35 deletions(-) create mode 100644 butterflow-ui/ButterflowSubregion.cs create mode 100644 butterflow-ui/PropertyChangedAlerter.cs create mode 100644 butterflow-ui/RegionType.cs diff --git a/butterflow-ui/ButterflowSubregion.cs b/butterflow-ui/ButterflowSubregion.cs new file mode 100644 index 0000000..9a57a66 --- /dev/null +++ b/butterflow-ui/ButterflowSubregion.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace butterflow_ui +{ + public class ButterflowSubregion : PropertyChangedAlerter, System.ComponentModel.INotifyPropertyChanged + { + #region Members + + /// The start of the subregion. + private TimeSpan start = TimeSpan.Zero; + /// The end of the subregion. + private TimeSpan end = TimeSpan.Zero; + /// Type of opersion to perform on the subregion. + private RegionType subregionType; + /// The value targeted for the subregion. + private decimal value; + /// True if the subregion runs to the end, false if not. + private bool toEnd; + + #endregion + + #region Properties + + /// Gets or sets the start of the subregion. + /// The start of the subregion. + public TimeSpan Start + { + get + { + return this.start; + } + set + { + this.start = value; + OnPropertyChanged(); + } + } + + /// Gets or sets the end of the subregion. + /// The end of the subregion. + public TimeSpan End + { + get + { + return this.end; + } + set + { + this.end = value; + OnPropertyChanged(); + } + } + + /// Gets or sets the operation to be performed on the subregion. + /// The operation to be performed on subregion. + public RegionType SubregionType + { + get + { + return this.subregionType; + } + set + { + this.subregionType = value; + OnPropertyChanged(); + } + } + + /// Gets or sets the targeted value of the subregion. + /// The value targeted for the subregion. + public decimal Value + { + get + { + return this.value; + } + set + { + this.value = value; + OnPropertyChanged(); + } + } + + /// Gets or sets a value indicating whether the subregion runs to the end of the video. + /// True if the subregion runs to the end, false if not. + public bool ToEnd + { + get + { + return this.toEnd; + } + set + { + this.toEnd = value; + OnPropertyChanged(); + } + } + + #endregion + } +} diff --git a/butterflow-ui/ButterflowWrapper.cs b/butterflow-ui/ButterflowWrapper.cs index 818ec71..d25cf30 100644 --- a/butterflow-ui/ButterflowWrapper.cs +++ b/butterflow-ui/ButterflowWrapper.cs @@ -9,12 +9,35 @@ using System.Threading.Tasks; namespace butterflow_ui { - public class ButterflowWrapper + public class ButterflowWrapper : PropertyChangedAlerter { #region Members /// Full pathname of the butterflow executable file. - private Lazy executablePath = new Lazy(() => Path.Combine(Assembly.GetExecutingAssembly().Location, "ThirdPartyCompiled", "butterflow.exe")); + private Lazy executablePath = new Lazy(() => Path.Combine(Directory.GetCurrentDirectory(), "ThirdPartyCompiled", "butterflow.exe")); + /// The console output from butterflow. + private string consoleOutput = string.Empty; + /// Event queue for all listeners interested in ConsoleOutputRecieved events. + //public event EventHandler ConsoleOutputRecieved; + + #endregion + + #region Properties + + /// Gets the console output from butterflow. + /// The console output from butterflow. + public string ConsoleOutput + { + get + { + return this.consoleOutput; + } + private set + { + this.consoleOutput = value; + OnPropertyChanged(); + } + } #endregion @@ -29,20 +52,74 @@ namespace butterflow_ui Run(arguments); } + /// Probes a video file. + /// The video file to be probed. public void Probe(string videoFile) { string arguments = string.Format("-prb \"{0}\"", videoFile); + Run(arguments); } /// Runs butterflow with the given . /// Options for controlling the operation. private void Run(string arguments) { - var processStartInfo = new ProcessStartInfo(executablePath.Value, arguments); + var process = new Process(); + process.StartInfo = new ProcessStartInfo(executablePath.Value, arguments); - processStartInfo.CreateNoWindow = true; - processStartInfo.UseShellExecute = false; - processStartInfo.RedirectStandardOutput = true; + process.StartInfo.CreateNoWindow = true; + process.StartInfo.UseShellExecute = false; + process.StartInfo.RedirectStandardOutput = true; + process.EnableRaisingEvents = true; + process.OutputDataReceived += ProcessOutputDataReceived; + + process.Start(); + process.BeginOutputReadLine(); + } + + /// Process the output data received from the butterflow executable. + /// Source of the event. + /// Data received event information. + private void ProcessOutputDataReceived(object sender, DataReceivedEventArgs e) + { + this.ConsoleOutput += string.Format("{0}{1}", e.Data, Environment.NewLine); + //OnConsoleOutputRecieved(e.Data); + } + + /// Executes the console output recieved event handler. + /// The output that has been recieved from butterflow. + //protected void OnConsoleOutputRecieved(string output) + //{ + // if (this.ConsoleOutputRecieved != null) + // { + // this.ConsoleOutputRecieved(this, new ButterflowConsoleOutputArgs(output)); + // } + //} + + #endregion + + #region Subclasses + + public class ButterflowConsoleOutputArgs : EventArgs + { + #region Properties + + /// Gets the console output. + /// The console output. + public string ConsoleOutput { get; private set; } + + #endregion + + #region Constructors + + /// Constructor. + /// The console output. + public ButterflowConsoleOutputArgs(string consoleOutput) + { + this.ConsoleOutput = consoleOutput; + } + + #endregion } #endregion diff --git a/butterflow-ui/Icons.xaml b/butterflow-ui/Icons.xaml index bd141f6..cbd5c96 100644 --- a/butterflow-ui/Icons.xaml +++ b/butterflow-ui/Icons.xaml @@ -112,4 +112,22 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/butterflow-ui/Localization/Localization.Designer.cs b/butterflow-ui/Localization/Localization.Designer.cs index 4e2debd..a515681 100644 --- a/butterflow-ui/Localization/Localization.Designer.cs +++ b/butterflow-ui/Localization/Localization.Designer.cs @@ -159,6 +159,15 @@ namespace butterflow_ui.Localization { } } + /// + /// Looks up a localized string similar to Clip a subregion in the video.. + /// + public static string ClipTooltip { + get { + return ResourceManager.GetString("ClipTooltip", resourceCulture); + } + } + /// /// Looks up a localized string similar to Common Options. /// @@ -186,6 +195,15 @@ namespace butterflow_ui.Localization { } } + /// + /// Looks up a localized string similar to E. + /// + public static string EndLabel { + get { + return ResourceManager.GetString("EndLabel", resourceCulture); + } + } + /// /// Looks up a localized string similar to Input. /// @@ -285,6 +303,15 @@ namespace butterflow_ui.Localization { } } + /// + /// Looks up a localized string similar to Play/Pause the video.. + /// + public static string PlayPauseTooltip { + get { + return ResourceManager.GetString("PlayPauseTooltip", resourceCulture); + } + } + /// /// Looks up a localized string similar to Video Rendering. /// @@ -303,6 +330,60 @@ namespace butterflow_ui.Localization { } } + /// + /// Looks up a localized string similar to Skip backward in the video.. + /// + public static string SkipBackTooltip { + get { + return ResourceManager.GetString("SkipBackTooltip", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Skip forward in the video.. + /// + public static string SkipForwardTooltip { + get { + return ResourceManager.GetString("SkipForwardTooltip", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to S. + /// + public static string StartLabel { + get { + return ResourceManager.GetString("StartLabel", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Stop the video.. + /// + public static string StopTooltip { + get { + return ResourceManager.GetString("StopTooltip", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Individual regions of the video to process.. + /// + public static string SubregionsDescription { + get { + return ResourceManager.GetString("SubregionsDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Subregions. + /// + public static string SubregionsLabel { + get { + return ResourceManager.GetString("SubregionsLabel", resourceCulture); + } + } + /// /// Looks up a localized string similar to butterflow-ui. /// @@ -312,6 +393,15 @@ namespace butterflow_ui.Localization { } } + /// + /// Looks up a localized string similar to To End. + /// + public static string ToEndLabel { + get { + return ResourceManager.GetString("ToEndLabel", resourceCulture); + } + } + /// /// Looks up a localized string similar to Width. /// diff --git a/butterflow-ui/Localization/Localization.resx b/butterflow-ui/Localization/Localization.resx index 9a9b545..0aefc13 100644 --- a/butterflow-ui/Localization/Localization.resx +++ b/butterflow-ui/Localization/Localization.resx @@ -129,6 +129,9 @@ Advanced Options + + Clip a subregion in the video. + Common Options @@ -138,6 +141,9 @@ _Edit + + E + Input @@ -171,15 +177,39 @@ Playback Rate + + Play/Pause the video. + Video Rendering Output Resolution + + Skip backward in the video. + + + Skip forward in the video. + + + S + + + Stop the video. + + + Individual regions of the video to process. + + + Subregions + butterflow-ui + + To End + Width diff --git a/butterflow-ui/MainWindow.xaml b/butterflow-ui/MainWindow.xaml index c740cc6..aaa9bd5 100644 --- a/butterflow-ui/MainWindow.xaml +++ b/butterflow-ui/MainWindow.xaml @@ -3,12 +3,21 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:System="clr-namespace:System;assembly=mscorlib" xmlns:gu="https://github.com/JohanLarsson/Gu.Wpf.Media" xmlns:loc="clr-namespace:butterflow_ui.Localization" xmlns:butterflow_ui="clr-namespace:butterflow_ui" mc:Ignorable="d" x:Name="butterflowUIWindow" Title="{x:Static loc:Localization.Title}" Height="600" Width="800"> + + + + + + + @@ -134,18 +143,39 @@ - + + + + + + + + + + + + + + + + + + + - - + + + + + @@ -157,7 +187,7 @@ Maximum="{Binding Path=Length, ElementName=mediaPreview, Converter={x:Static gu:NullableTimeSpanToSecondsConverter.Default}}" /> - + @@ -170,6 +200,9 @@ + diff --git a/butterflow-ui/MainWindow.xaml.cs b/butterflow-ui/MainWindow.xaml.cs index f4d9633..2f77bf1 100644 --- a/butterflow-ui/MainWindow.xaml.cs +++ b/butterflow-ui/MainWindow.xaml.cs @@ -23,15 +23,20 @@ namespace butterflow_ui { #region Members - // + /// True if is the user has started clipping, false if not. + private bool isClipping; + /// The temporary storage for the clip start time. + private TimeSpan clipStart; #endregion #region Properties - /// Gets or sets the butyterflow options configuration. + /// Gets or sets the butterflow options configuration. /// The options configuration. public OptionsConfiguration OptionsConfiguration { get; set; } = new OptionsConfiguration(); + /// The butterflow wrapper used to call butterflow. + public ButterflowWrapper ButterflowWrapper { get; set; } = new ButterflowWrapper(); #endregion @@ -48,13 +53,15 @@ namespace butterflow_ui { var ofd = new OpenFileDialog(); ofd.Filter = "Supported Video Files|*.mp4;*.mkv"; - var result = ofd.ShowDialog(this); if (result.HasValue && result.Value) { this.OptionsConfiguration.VideoInput = ofd.FileName; + //this.ButterflowWrapper.ConsoleOutputRecieved += (o, ce) => this.txtConsoleOutput.Text = ce.ConsoleOutput; + this.ButterflowWrapper.Probe(ofd.FileName); + //Hack to get the first frame to display in the media preview element. //This also triggers the MediaOpened event so we can get the metadata from the element. mediaPreview.Play(); @@ -69,7 +76,7 @@ namespace butterflow_ui { var typedSender = (RadioButton)sender; - if(typedSender != null) + if (typedSender != null) { var tag = typedSender.Tag.ToString(); this.OptionsConfiguration.PlaybackRate = tag; @@ -86,7 +93,7 @@ namespace butterflow_ui this.mediaPreview.TogglePlayPause(); } - if(this.mediaPreview.IsPlaying) + if (this.mediaPreview.IsPlaying) { this.PlayPauseButtonIcon.Template = Application.Current.Resources["PauseIcon"] as ControlTemplate; } @@ -127,6 +134,31 @@ namespace butterflow_ui } } + /// Event handler. Called by bntClip for click events. + /// Source of the event. + /// Routed event information. + private void bntClip_Click(object sender, RoutedEventArgs e) + { + if (this.mediaPreview.Position.HasValue) + { + if (!this.isClipping) + { + //start clipping + this.isClipping = true; + this.ClippingButtonIcon.Template = Application.Current.Resources["SnipCloseIcon"] as ControlTemplate; + this.clipStart = this.mediaPreview.Position.Value; + } + else + { + //end clipping + this.isClipping = false; + this.ClippingButtonIcon.Template = Application.Current.Resources["SnipOpenIcon"] as ControlTemplate; + this.OptionsConfiguration.Subregions.Add(new ButterflowSubregion() { Start = this.clipStart, End = this.mediaPreview.Position.Value, ToEnd = false, Value = 1, SubregionType = RegionType.spd }); + this.clipStart = TimeSpan.Zero; + } + } + } + /// Event handler. Called by mediaPreview for media opened events. /// Source of the event. /// Routed event information. @@ -143,5 +175,43 @@ namespace butterflow_ui { this.PlayPauseButtonIcon.Template = Application.Current.Resources["PlayIcon"] as ControlTemplate; } + + /// Event handler. Called by ScrollViewer for scroll changed events. + /// Source of the event. + /// Scroll changed event information. + /// + /// This code autoscrolls the scroll viewer as more text is added. It is based on this example + /// from Stack Overflow: + /// https://stackoverflow.com/questions/2984803/how-to-automatically-scroll-scrollviewer-only-if-the-user-did-not-change-scrol. + /// + private void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e) + { + var scrollViewer = sender as ScrollViewer; + bool autoScroll = true; + + if (scrollViewer != null) + { + if (e.ExtentHeightChange == 0) + { // Content unchanged : user scroll event + if (scrollViewer.VerticalOffset == scrollViewer.ScrollableHeight) + { // Scroll bar is in bottom + // Set auto-scroll mode + autoScroll = true; + } + else + { // Scroll bar isn't in bottom + // Unset auto-scroll mode + autoScroll = false; + } + } + + // Content scroll event : auto-scroll eventually + if (autoScroll && e.ExtentHeightChange != 0) + { // Content changed and auto-scroll mode set + // Autoscroll + scrollViewer.ScrollToEnd(); + } + } + } } } diff --git a/butterflow-ui/OptionsConfiguration.cs b/butterflow-ui/OptionsConfiguration.cs index 3a6e0ce..f2bca59 100644 --- a/butterflow-ui/OptionsConfiguration.cs +++ b/butterflow-ui/OptionsConfiguration.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; using System.Text; @@ -11,13 +12,10 @@ namespace butterflow_ui /// (Serializable) the options configuration. [Serializable] - public class OptionsConfiguration : INotifyPropertyChanged + public class OptionsConfiguration : PropertyChangedAlerter { #region Members - /// Occurs when a property value changes. - public event PropertyChangedEventHandler PropertyChanged; - /// An interpreter used to ensure numeric input is correctly calculated. private InputInterpreter interpreter = new InputInterpreter(); @@ -29,6 +27,7 @@ namespace butterflow_ui private bool losslessQuality; private string videoInput; private string videoOutput; + private ObservableCollection subregions = new ObservableCollection(); #endregion @@ -55,7 +54,7 @@ namespace butterflow_ui set { this.playbackRate = value; - OnPropertyChanged("PlaybackRate"); + OnPropertyChanged(); } } @@ -70,7 +69,7 @@ namespace butterflow_ui set { this.keepAudio = value; - OnPropertyChanged("KeepAudio"); + OnPropertyChanged(); } } @@ -86,7 +85,7 @@ namespace butterflow_ui { interpreter.Interpret(value); this.width = interpreter.Int; - OnPropertyChanged("Width"); + OnPropertyChanged(); } } @@ -102,7 +101,7 @@ namespace butterflow_ui { interpreter.Interpret(value); this.height = interpreter.Int; - OnPropertyChanged("Height"); + OnPropertyChanged(); } } @@ -117,7 +116,7 @@ namespace butterflow_ui set { this.keepAspectRatio = value; - OnPropertyChanged("KeepAspectRatio"); + OnPropertyChanged(); } } @@ -132,7 +131,7 @@ namespace butterflow_ui set { this.losslessQuality = value; - OnPropertyChanged("LosslessQuality"); + OnPropertyChanged(); } } @@ -147,7 +146,7 @@ namespace butterflow_ui set { this.videoInput = value; - OnPropertyChanged("VideoInput"); + OnPropertyChanged(); } } @@ -162,20 +161,64 @@ namespace butterflow_ui set { this.videoOutput = value; - OnPropertyChanged("VideoOutput"); + 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 + + #region Contructors + + /// Default constructor. + public OptionsConfiguration() + { + AddConstantCallProperty("CommandLineOutput"); + + this.subregions.CollectionChanged += SubregionsCollectionChanged; + } + + private void SubregionsCollectionChanged(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 #region Methods - /// Executes the property changed action. - /// The name. - protected void OnPropertyChanged(string name) + private void SubregionPropertyChanged(object sender, PropertyChangedEventArgs e) { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("CommandLineOutput")); + OnPropertyChanged("CommandLineOutput"); } /// Converts this object to a butterflow options. @@ -184,7 +227,7 @@ namespace butterflow_ui { var stringBuilder = new StringBuilder("-v "); //Verbose - if(this.KeepAspectRatio) + if (this.KeepAspectRatio) { stringBuilder.AppendFormat("-vs {0}:-1 ", this.Width); } @@ -193,13 +236,37 @@ namespace butterflow_ui stringBuilder.AppendFormat("-vs {0}:{1} ", this.Width, this.Height); } - stringBuilder.AppendFormat("-r {0} ", this.PlaybackRate); - + if (!string.IsNullOrWhiteSpace(this.PlaybackRate)) stringBuilder.AppendFormat("-r {0} ", this.PlaybackRate); if (this.KeepAudio) stringBuilder.Append("-audio "); if (this.LosslessQuality) stringBuilder.Append("-l "); stringBuilder.AppendFormat("\"{0}\"", this.VideoInput); + if (this.Subregions.Any()) + { + 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(" "); + } + return stringBuilder.ToString(); } diff --git a/butterflow-ui/PropertyChangedAlerter.cs b/butterflow-ui/PropertyChangedAlerter.cs new file mode 100644 index 0000000..0b90ba9 --- /dev/null +++ b/butterflow-ui/PropertyChangedAlerter.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; + +namespace butterflow_ui +{ + public abstract class PropertyChangedAlerter : INotifyPropertyChanged + { + #region Members + + /// Occurs when a property value changes. + public event PropertyChangedEventHandler PropertyChanged; + /// A list of properties to always call as updated. Generally used for composite properties. + private List alwaysCall = new List(); + + #endregion + + #region Properties + + // + + #endregion + + #region Methods + + /// Executes the property changed action. + /// The name. + protected virtual void OnPropertyChanged([CallerMemberName]string name = null) + { + if (!string.IsNullOrWhiteSpace(name)) + { + this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); + foreach (var updatedProperty in this.alwaysCall) + { + this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(updatedProperty)); + } + } + } + + /// Adds a property that will always be called when any property is updated.. + /// The name of the property. + public void AddConstantCallProperty(string name) + { + if (!this.alwaysCall.Any(c => c.Equals(name, StringComparison.OrdinalIgnoreCase))) + { + this.alwaysCall.Add(name); + } + } + + #endregion + } +} diff --git a/butterflow-ui/RegionType.cs b/butterflow-ui/RegionType.cs new file mode 100644 index 0000000..97363e0 --- /dev/null +++ b/butterflow-ui/RegionType.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace butterflow_ui +{ + /// Values that represent subregion types. + public enum RegionType + { + /// Speed. + spd, + /// Duration. + dur, + /// Frames per second. + fps + } + +} diff --git a/butterflow-ui/butterflow-ui.csproj b/butterflow-ui/butterflow-ui.csproj index f6bbfa0..b88aa9b 100644 --- a/butterflow-ui/butterflow-ui.csproj +++ b/butterflow-ui/butterflow-ui.csproj @@ -60,6 +60,8 @@ MSBuild:Compile Designer + + MSBuild:Compile Designer @@ -77,6 +79,7 @@ Code + True