Subregions auto update. ButterflowWrapper now calls butterflow.exe

This commit is contained in:
Jordan Wages 2018-07-02 23:31:16 -05:00
parent 9145071e23
commit d0b29f20fd
11 changed files with 604 additions and 35 deletions

View File

@ -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
/// <summary> The start of the subregion. </summary>
private TimeSpan start = TimeSpan.Zero;
/// <summary> The end of the subregion. </summary>
private TimeSpan end = TimeSpan.Zero;
/// <summary> Type of opersion to perform on the subregion. </summary>
private RegionType subregionType;
/// <summary> The value targeted for the subregion. </summary>
private decimal value;
/// <summary> True if the subregion runs to the end, false if not. </summary>
private bool toEnd;
#endregion
#region Properties
/// <summary> Gets or sets the start of the subregion. </summary>
/// <value> The start of the subregion. </value>
public TimeSpan Start
{
get
{
return this.start;
}
set
{
this.start = value;
OnPropertyChanged();
}
}
/// <summary> Gets or sets the end of the subregion. </summary>
/// <value> The end of the subregion. </value>
public TimeSpan End
{
get
{
return this.end;
}
set
{
this.end = value;
OnPropertyChanged();
}
}
/// <summary> Gets or sets the operation to be performed on the subregion. </summary>
/// <value> The operation to be performed on subregion. </value>
public RegionType SubregionType
{
get
{
return this.subregionType;
}
set
{
this.subregionType = value;
OnPropertyChanged();
}
}
/// <summary> Gets or sets the targeted value of the subregion. </summary>
/// <value> The value targeted for the subregion. </value>
public decimal Value
{
get
{
return this.value;
}
set
{
this.value = value;
OnPropertyChanged();
}
}
/// <summary> Gets or sets a value indicating whether the subregion runs to the end of the video. </summary>
/// <value> True if the subregion runs to the end, false if not. </value>
public bool ToEnd
{
get
{
return this.toEnd;
}
set
{
this.toEnd = value;
OnPropertyChanged();
}
}
#endregion
}
}

View File

@ -9,12 +9,35 @@ using System.Threading.Tasks;
namespace butterflow_ui
{
public class ButterflowWrapper
public class ButterflowWrapper : PropertyChangedAlerter
{
#region Members
/// <summary> Full pathname of the butterflow executable file. </summary>
private Lazy<string> executablePath = new Lazy<string>(() => Path.Combine(Assembly.GetExecutingAssembly().Location, "ThirdPartyCompiled", "butterflow.exe"));
private Lazy<string> executablePath = new Lazy<string>(() => Path.Combine(Directory.GetCurrentDirectory(), "ThirdPartyCompiled", "butterflow.exe"));
/// <summary> The console output from butterflow. </summary>
private string consoleOutput = string.Empty;
/// <summary> Event queue for all listeners interested in ConsoleOutputRecieved events. </summary>
//public event EventHandler<ButterflowConsoleOutputArgs> ConsoleOutputRecieved;
#endregion
#region Properties
/// <summary> Gets the console output from butterflow. </summary>
/// <value> The console output from butterflow. </value>
public string ConsoleOutput
{
get
{
return this.consoleOutput;
}
private set
{
this.consoleOutput = value;
OnPropertyChanged();
}
}
#endregion
@ -29,20 +52,74 @@ namespace butterflow_ui
Run(arguments);
}
/// <summary> Probes a video file. </summary>
/// <param name="videoFile"> The video file to be probed. </param>
public void Probe(string videoFile)
{
string arguments = string.Format("-prb \"{0}\"", videoFile);
Run(arguments);
}
/// <summary> Runs butterflow with the given <paramref name="arguments"/>. </summary>
/// <param name="arguments"> Options for controlling the operation. </param>
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();
}
/// <summary> Process the output data received from the butterflow executable. </summary>
/// <param name="sender"> Source of the event. </param>
/// <param name="e"> Data received event information. </param>
private void ProcessOutputDataReceived(object sender, DataReceivedEventArgs e)
{
this.ConsoleOutput += string.Format("{0}{1}", e.Data, Environment.NewLine);
//OnConsoleOutputRecieved(e.Data);
}
/// <summary> Executes the console output recieved event handler. </summary>
/// <param name="output"> The output that has been recieved from butterflow. </param>
//protected void OnConsoleOutputRecieved(string output)
//{
// if (this.ConsoleOutputRecieved != null)
// {
// this.ConsoleOutputRecieved(this, new ButterflowConsoleOutputArgs(output));
// }
//}
#endregion
#region Subclasses
public class ButterflowConsoleOutputArgs : EventArgs
{
#region Properties
/// <summary> Gets the console output. </summary>
/// <value> The console output. </value>
public string ConsoleOutput { get; private set; }
#endregion
#region Constructors
/// <summary> Constructor. </summary>
/// <param name="consoleOutput"> The console output. </param>
public ButterflowConsoleOutputArgs(string consoleOutput)
{
this.ConsoleOutput = consoleOutput;
}
#endregion
}
#endregion

View File

@ -112,4 +112,22 @@
</Path>
</Viewbox>
</ControlTemplate>
<ControlTemplate x:Key="SnipOpenIcon">
<Viewbox>
<Path Fill="#000000">
<Path.Data>
<PathGeometry Figures="M12.026 14.116c-3.475 1.673-7.504 3.619-8.484 4.09-1.848.889-3.542-1.445-3.542-1.445l8.761-4.226 3.265 1.581zm7.93 6.884c-.686 0-1.393-.154-2.064-.479-1.943-.941-2.953-3.001-2.498-4.854.26-1.057-.296-1.201-1.145-1.612l-14.189-6.866s1.7-2.329 3.546-1.436c1.134.549 5.689 2.747 9.614 4.651l.985-.474c.85-.409 1.406-.552 1.149-1.609-.451-1.855.564-3.913 2.51-4.848.669-.321 1.373-.473 2.054-.473 2.311 0 4.045 1.696 4.045 3.801 0 1.582-.986 3.156-2.613 3.973-1.625.816-2.765.18-4.38.965l-.504.245.552.27c1.613.789 2.754.156 4.377.976 1.624.819 2.605 2.392 2.605 3.97 0 2.108-1.739 3.8-4.044 3.8zm-2.555-12.815c.489 1.022 1.876 1.378 3.092.793 1.217-.584 1.809-1.893 1.321-2.916-.489-1.022-1.876-1.379-3.093-.794s-1.808 1.894-1.32 2.917zm-3.643 3.625c0-.414-.335-.75-.75-.75-.414 0-.75.336-.75.75s.336.75.75.75.75-.336.75-.75zm6.777 3.213c-1.215-.588-2.604-.236-3.095.786-.491 1.022.098 2.332 1.313 2.919 1.215.588 2.603.235 3.094-.787.492-1.021-.097-2.33-1.312-2.918z" FillRule="NonZero"/>
</Path.Data>
</Path>
</Viewbox>
</ControlTemplate>
<ControlTemplate x:Key="SnipCloseIcon">
<Viewbox>
<Path Fill="#000000">
<Path.Data>
<PathGeometry Figures="M14.686 13.646l-6.597 3.181c-1.438.692-2.755-1.124-2.755-1.124l6.813-3.287 2.539 1.23zm6.168 5.354c-.533 0-1.083-.119-1.605-.373-1.511-.731-2.296-2.333-1.943-3.774.203-.822-.23-.934-.891-1.253l-11.036-5.341s1.322-1.812 2.759-1.117c.881.427 4.423 2.136 7.477 3.617l.766-.368c.662-.319 1.094-.43.895-1.252-.351-1.442.439-3.043 1.952-3.77.521-.251 1.068-.369 1.596-.369 1.799 0 3.147 1.32 3.147 2.956 0 1.23-.766 2.454-2.032 3.091-1.266.634-2.15.14-3.406.75l-.394.19.431.21c1.254.614 2.142.122 3.404.759 1.262.638 2.026 1.861 2.026 3.088 0 1.64-1.352 2.956-3.146 2.956zm-1.987-9.967c.381.795 1.459 1.072 2.406.617.945-.455 1.405-1.472 1.027-2.267-.381-.796-1.46-1.073-2.406-.618-.946.455-1.408 1.472-1.027 2.268zm-2.834 2.819c0-.322-.261-.583-.583-.583-.321 0-.583.261-.583.583s.262.583.583.583c.322.001.583-.261.583-.583zm5.272 2.499c-.945-.457-2.025-.183-2.408.611-.381.795.078 1.814 1.022 2.271.945.458 2.024.184 2.406-.611.382-.795-.075-1.814-1.02-2.271zm-18.305-3.351h-3v2h3v-2zm4 0h-3v2h3v-2z" FillRule="NonZero"/>
</Path.Data>
</Path>
</Viewbox>
</ControlTemplate>
</ResourceDictionary>

View File

@ -159,6 +159,15 @@ namespace butterflow_ui.Localization {
}
}
/// <summary>
/// Looks up a localized string similar to Clip a subregion in the video..
/// </summary>
public static string ClipTooltip {
get {
return ResourceManager.GetString("ClipTooltip", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Common Options.
/// </summary>
@ -186,6 +195,15 @@ namespace butterflow_ui.Localization {
}
}
/// <summary>
/// Looks up a localized string similar to E.
/// </summary>
public static string EndLabel {
get {
return ResourceManager.GetString("EndLabel", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Input.
/// </summary>
@ -285,6 +303,15 @@ namespace butterflow_ui.Localization {
}
}
/// <summary>
/// Looks up a localized string similar to Play/Pause the video..
/// </summary>
public static string PlayPauseTooltip {
get {
return ResourceManager.GetString("PlayPauseTooltip", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Video Rendering.
/// </summary>
@ -303,6 +330,60 @@ namespace butterflow_ui.Localization {
}
}
/// <summary>
/// Looks up a localized string similar to Skip backward in the video..
/// </summary>
public static string SkipBackTooltip {
get {
return ResourceManager.GetString("SkipBackTooltip", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Skip forward in the video..
/// </summary>
public static string SkipForwardTooltip {
get {
return ResourceManager.GetString("SkipForwardTooltip", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to S.
/// </summary>
public static string StartLabel {
get {
return ResourceManager.GetString("StartLabel", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Stop the video..
/// </summary>
public static string StopTooltip {
get {
return ResourceManager.GetString("StopTooltip", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Individual regions of the video to process..
/// </summary>
public static string SubregionsDescription {
get {
return ResourceManager.GetString("SubregionsDescription", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Subregions.
/// </summary>
public static string SubregionsLabel {
get {
return ResourceManager.GetString("SubregionsLabel", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to butterflow-ui.
/// </summary>
@ -312,6 +393,15 @@ namespace butterflow_ui.Localization {
}
}
/// <summary>
/// Looks up a localized string similar to To End.
/// </summary>
public static string ToEndLabel {
get {
return ResourceManager.GetString("ToEndLabel", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Width.
/// </summary>

View File

@ -129,6 +129,9 @@
<data name="AdvancedOptionsGroupBox" xml:space="preserve">
<value>Advanced Options</value>
</data>
<data name="ClipTooltip" xml:space="preserve">
<value>Clip a subregion in the video.</value>
</data>
<data name="CommonOptionsGroupBox" xml:space="preserve">
<value>Common Options</value>
</data>
@ -138,6 +141,9 @@
<data name="EditMenu" xml:space="preserve">
<value>_Edit</value>
</data>
<data name="EndLabel" xml:space="preserve">
<value>E</value>
</data>
<data name="FileInputGroupBox" xml:space="preserve">
<value>Input</value>
</data>
@ -171,15 +177,39 @@
<data name="PlaybackRateLabel" xml:space="preserve">
<value>Playback Rate</value>
</data>
<data name="PlayPauseTooltip" xml:space="preserve">
<value>Play/Pause the video.</value>
</data>
<data name="RenderingLabel" xml:space="preserve">
<value>Video Rendering</value>
</data>
<data name="ResolutionLabel" xml:space="preserve">
<value>Output Resolution</value>
</data>
<data name="SkipBackTooltip" xml:space="preserve">
<value>Skip backward in the video.</value>
</data>
<data name="SkipForwardTooltip" xml:space="preserve">
<value>Skip forward in the video.</value>
</data>
<data name="StartLabel" xml:space="preserve">
<value>S</value>
</data>
<data name="StopTooltip" xml:space="preserve">
<value>Stop the video.</value>
</data>
<data name="SubregionsDescription" xml:space="preserve">
<value>Individual regions of the video to process.</value>
</data>
<data name="SubregionsLabel" xml:space="preserve">
<value>Subregions</value>
</data>
<data name="Title" xml:space="preserve">
<value>butterflow-ui</value>
</data>
<data name="ToEndLabel" xml:space="preserve">
<value>To End</value>
</data>
<data name="WidthLabel" xml:space="preserve">
<value>Width</value>
</data>

View File

@ -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">
<Window.Resources>
<ObjectDataProvider x:Key="enumDataProvider" MethodName="GetValues"
ObjectType="{x:Type System:Enum}">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="butterflow_ui:RegionType"/>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</Window.Resources>
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Header="{x:Static loc:Localization.FileMenu}">
@ -134,18 +143,39 @@
<GroupBox Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" Header="{x:Static loc:Localization.AdvancedOptionsGroupBox}">
<ScrollViewer>
<StackPanel>
<butterflow_ui:ButterflowOption LabelValue="{x:Static loc:Localization.SubregionsLabel}" DescriptionValue="{x:Static loc:Localization.SubregionsDescription}">
<ListView Name="listSubregions" MinHeight="25" ItemsSource="{Binding OptionsConfiguration.Subregions, Mode=OneWay, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type butterflow_ui:MainWindow}}, UpdateSourceTrigger=PropertyChanged}">
<ListView.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock Text="{x:Static loc:Localization.StartLabel}" />
<TextBlock Text="{Binding Start, Converter={x:Static gu:TimeSpanToStringConverter.Default}}" ToolTip="{Binding Start}"/>
<TextBlock Text="{x:Static loc:Localization.EndLabel}" />
<TextBlock Text="{Binding End, Converter={x:Static gu:TimeSpanToStringConverter.Default}}" ToolTip="{Binding End}"/>
<ComboBox ItemsSource="{Binding Source={StaticResource enumDataProvider}}" SelectedItem="{Binding SubregionType, Mode=TwoWay}"/>
<TextBox MinWidth="35" Text="{Binding Value, Mode=TwoWay}" />
<TextBlock Text="{x:Static loc:Localization.ToEndLabel}" />
<CheckBox IsChecked="{Binding ToEnd, Mode=TwoWay}" />
</WrapPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</butterflow_ui:ButterflowOption>
</StackPanel>
</ScrollViewer>
</GroupBox>
<Grid Grid.Row="1" Grid.RowSpan="2" Grid.Column="2" Grid.ColumnSpan="2">
<Grid.RowDefinitions>
<RowDefinition Height="0.3*" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<gu:MediaElementWrapper Grid.Row="0" Name="mediaPreview" Stretch="Uniform" ScrubbingEnabled="True" Source="{Binding OptionsConfiguration.VideoInput, Mode=TwoWay, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type butterflow_ui:MainWindow}}, UpdateSourceTrigger=PropertyChanged}" MediaOpened="mediaPreview_MediaOpened" MediaEnded="mediaPreview_MediaEnded" />
<Grid Grid.Row="1">
<ScrollViewer VerticalScrollBarVisibility="Auto" ScrollChanged="ScrollViewer_ScrollChanged">
<TextBlock Grid.Row="0" Name="txtConsoleOutput" Background="Black" Foreground="White" TextWrapping="Wrap" Text="{Binding ButterflowWrapper.ConsoleOutput, Mode=OneWay, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type butterflow_ui:MainWindow}}, UpdateSourceTrigger=PropertyChanged}" />
</ScrollViewer>
<gu:MediaElementWrapper Grid.Row="1" Name="mediaPreview" Stretch="Uniform" ScrubbingEnabled="True" Source="{Binding OptionsConfiguration.VideoInput, Mode=TwoWay, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type butterflow_ui:MainWindow}}, UpdateSourceTrigger=PropertyChanged}" MediaOpened="mediaPreview_MediaOpened" MediaEnded="mediaPreview_MediaEnded" />
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
@ -157,7 +187,7 @@
Maximum="{Binding Path=Length, ElementName=mediaPreview, Converter={x:Static gu:NullableTimeSpanToSecondsConverter.Default}}" />
<Label Grid.Column="2" Content="{Binding Path=Length, ElementName=mediaPreview, Converter={x:Static gu:TimeSpanToStringConverter.Default}}" />
</Grid>
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Center">
<StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Center">
<Button Name="bntVideoBackward" BorderThickness="0" Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}" Click="bntVideoBackward_Click">
<ContentControl HorizontalAlignment="Center" Template="{StaticResource BackwardIcon}" />
</Button>
@ -170,6 +200,9 @@
<Button Name="bntVideoForward" BorderThickness="0" Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}" Click="bntVideoForward_Click">
<ContentControl HorizontalAlignment="Center" Template="{StaticResource ForwardIcon}" />
</Button>
<Button Name="bntClip" BorderThickness="0" Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}" Click="bntClip_Click">
<ContentControl HorizontalAlignment="Center" Name="ClippingButtonIcon" Template="{StaticResource SnipOpenIcon}" />
</Button>
</StackPanel>
</Grid>
<StatusBar Grid.Row="3" Grid.ColumnSpan="4">

View File

@ -23,15 +23,20 @@ namespace butterflow_ui
{
#region Members
//
/// <summary> True if is the user has started clipping, false if not. </summary>
private bool isClipping;
/// <summary> The temporary storage for the clip start time. </summary>
private TimeSpan clipStart;
#endregion
#region Properties
/// <summary> Gets or sets the butyterflow options configuration. </summary>
/// <summary> Gets or sets the butterflow options configuration. </summary>
/// <value> The options configuration. </value>
public OptionsConfiguration OptionsConfiguration { get; set; } = new OptionsConfiguration();
/// <summary> The butterflow wrapper used to call butterflow. </summary>
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
}
}
/// <summary> Event handler. Called by bntClip for click events. </summary>
/// <param name="sender"> Source of the event. </param>
/// <param name="e"> Routed event information. </param>
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;
}
}
}
/// <summary> Event handler. Called by mediaPreview for media opened events. </summary>
/// <param name="sender"> Source of the event. </param>
/// <param name="e"> Routed event information. </param>
@ -143,5 +175,43 @@ namespace butterflow_ui
{
this.PlayPauseButtonIcon.Template = Application.Current.Resources["PlayIcon"] as ControlTemplate;
}
/// <summary> Event handler. Called by ScrollViewer for scroll changed events. </summary>
/// <param name="sender"> Source of the event. </param>
/// <param name="e"> Scroll changed event information. </param>
/// <remarks>
/// 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.
/// </remarks>
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();
}
}
}
}
}

View File

@ -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
/// <summary> (Serializable) the options configuration. </summary>
[Serializable]
public class OptionsConfiguration : INotifyPropertyChanged
public class OptionsConfiguration : PropertyChangedAlerter
{
#region Members
/// <summary> Occurs when a property value changes. </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary> An interpreter used to ensure numeric input is correctly calculated. </summary>
private InputInterpreter interpreter = new InputInterpreter();
@ -29,6 +27,7 @@ namespace butterflow_ui
private bool losslessQuality;
private string videoInput;
private string videoOutput;
private ObservableCollection<ButterflowSubregion> subregions = new ObservableCollection<ButterflowSubregion>();
#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();
}
}
/// <summary> Gets or sets the subregions of the video on which to work. </summary>
/// <value> The subregions of the video. </value>
public ObservableCollection<ButterflowSubregion> Subregions
{
get
{
return this.subregions;
}
set
{
this.subregions = value;
OnPropertyChanged();
}
}
#endregion
#region Contructors
/// <summary> Default constructor. </summary>
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
/// <summary> Executes the property changed action. </summary>
/// <param name="name"> The name. </param>
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");
}
/// <summary> Converts this object to a butterflow options. </summary>
@ -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();
}

View File

@ -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
/// <summary> Occurs when a property value changes. </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary> A list of properties to always call as updated. Generally used for composite properties. </summary>
private List<string> alwaysCall = new List<string>();
#endregion
#region Properties
//
#endregion
#region Methods
/// <summary> Executes the property changed action. </summary>
/// <param name="name"> The name. </param>
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));
}
}
}
/// <summary> Adds a property that will always be called when any property is updated.. </summary>
/// <param name="name"> The name of the property. </param>
public void AddConstantCallProperty(string name)
{
if (!this.alwaysCall.Any(c => c.Equals(name, StringComparison.OrdinalIgnoreCase)))
{
this.alwaysCall.Add(name);
}
}
#endregion
}
}

View File

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace butterflow_ui
{
/// <summary> Values that represent subregion types. </summary>
public enum RegionType
{
/// <summary> Speed. </summary>
spd,
/// <summary> Duration. </summary>
dur,
/// <summary> Frames per second. </summary>
fps
}
}

View File

@ -60,6 +60,8 @@
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
<Compile Include="PropertyChangedAlerter.cs" />
<Compile Include="RegionType.cs" />
<Page Include="Icons.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
@ -77,6 +79,7 @@
<SubType>Code</SubType>
</Compile>
<Compile Include="ButterflowOption.cs" />
<Compile Include="ButterflowSubregion.cs" />
<Compile Include="ButterflowWrapper.cs" />
<Compile Include="Localization\Localization.Designer.cs">
<AutoGen>True</AutoGen>