using Microsoft.Win32; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Runtime.Serialization.Formatters.Binary; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace butterflow_ui { /// /// Interaction logic for MainWindow.xaml /// public partial class MainWindow : Window { #region Members /// List of types of videos supported by ffmpeg, and thus butterflow. private const string SUPPORTED_FILE_TYPES = "*.3dostr;*.3g2;*.3gp;*.4xm;*.a64;*.aa;*.aac;*.ac3;*.acm;*.act;*.adf;*.adp;*.ads;*.adts;*.adx;*.aea;*.afc;*.aiff;*.aix;*.alaw;*.alias_pix;*.alsa;*.amr;*.anm;*.apc;*.ape;*.apng;*.aqtitle;*.asf;*.asf_o;*.asf_stream;*.ass;*.ast;*.au;*.avi;*.avisynth;*.avm2;*.avr;*.avs;*.bethsoftvid;*.bfi;*.bfstm;*.bin;*.bink;*.bit;*.bmp_pipe;*.bmv;*.boa;*.brender_pix;*.brstm;*.c93;*.caca;*.caf;*.cavsvideo;*.cdg;*.cdxl;*.chromaprint;*.cine;*.concat;*.crc;*.dash;*.data;*.daud;*.dcstr;*.dds_pipe;*.dfa;*.dirac;*.dnxhd;*.dpx_pipe;*.dsf;*.dsicin;*.dss;*.dts;*.dtshd;*.dv;*.dv1394;*.dvbsub;*.dvbtxt;*.dvd;*.dxa;*.ea;*.ea_cdata;*.eac3;*.epaf;*.exr_pipe;*.f32be;*.f32le;*.f4v;*.f64be;*.f64le;*.fbdev;*.ffm;*.ffmetadata;*.fifo;*.film_cpk;*.filmstrip;*.flac;*.flic;*.flv;*.framecrc;*.framehash;*.framemd5;*.frm;*.fsb;*.g722;*.g723_1;*.g729;*.genh;*.gif;*.gsm;*.gxf;*.h261;*.h263;*.h264;*.hash;*.hds;*.hevc;*.hls;*.hls;*.applehttp;*.hnm;*.ico;*.idcin;*.idf;*.iec61883;*.iff;*.ilbc;*.image2;*.image2pipe;*.ingenient;*.ipmovie;*.ipod;*.ircam;*.ismv;*.iss;*.iv8;*.ivf;*.ivr;*.j2k_pipe;*.jack;*.jacosub;*.jpeg_pipe;*.jpegls_pipe;*.jv;*.latm;*.lavfi;*.libcdio;*.libdc1394;*.libgme;*.libopenmpt;*.live_flv;*.lmlm4;*.loas;*.lrc;*.lvf;*.lxf;*.m4v;*.matroska;*.matroska;*.webm;*.md5;*.mgsts;*.microdvd;*.mjpeg;*.mkvtimestamp_v2;*.mkv;*.mlp;*.mlv;*.mm;*.mmf;*.mov;*.mov;*.mp4;*.m4a;*.3gp;*.3g2;*.mj2;*.mp2;*.mp3;*.mp4;*.mpc;*.mpc8;*.mpeg;*.mpeg1video;*.mpeg2video;*.mpegts;*.mpegtsraw;*.mpegvideo;*.mpjpeg;*.mpl2;*.mpsub;*.msf;*.msnwctcp;*.mtaf;*.mtv;*.mulaw;*.musx;*.mv;*.mvi;*.mxf;*.mxf_d10;*.mxf_opatom;*.mxg;*.nc;*.nistsphere;*.nsv;*.null;*.nut;*.nuv;*.oga;*.ogg;*.ogv;*.oma;*.openal;*.opengl;*.opus;*.oss;*.paf;*.pam_pipe;*.pbm_pipe;*.pcx_pipe;*.pgm_pipe;*.pgmyuv_pipe;*.pictor_pipe;*.pjs;*.pmp;*.png_pipe;*.ppm_pipe;*.psp;*.psxstr;*.pulse;*.pva;*.pvf;*.qcp;*.qdraw_pipe;*.r3d;*.rawvideo;*.realtext;*.redspark;*.rl2;*.rm;*.roq;*.rpl;*.rsd;*.rso;*.rtp;*.rtp_mpegts;*.rtsp;*.s16be;*.s16le;*.s24be;*.s24le;*.s32be;*.s32le;*.s8;*.sami;*.sap;*.sbg;*.sdl;*.sdl2;*.sdp;*.sdr2;*.segment;*.sgi_pipe;*.shn;*.siff;*.singlejpeg;*.sln;*.smjpeg;*.smk;*.smoothstreaming;*.smush;*.sndio;*.sol;*.sox;*.spdif;*.spx;*.srt;*.stl;*.stream_segment;*.ssegment;*.subviewer;*.subviewer1;*.sunrast_pipe;*.sup;*.svag;*.svcd;*.swf;*.tak;*.tedcaptions;*.tee;*.thp;*.tiertexseq;*.tiff_pipe;*.tmv;*.truehd;*.tta;*.tty;*.txd;*.u16be;*.u16le;*.u24be;*.u24le;*.u32be;*.u32le;*.u8;*.uncodedframecrc;*.v210;*.v210x;*.v4l2;*.vag;*.vc1;*.vc1test;*.vcd;*.video4linux2;*.v4l2;*.vivo;*.vmd;*.vob;*.vobsub;*.voc;*.vpk;*.vplayer;*.vqf;*.w64;*.wav;*.wc3movie;*.webm;*.webm_chunk;*.webm_dash_manifest;*.webp;*.webp_pipe;*.webvtt;*.wsaud;*.wsd;*.wsvqa;*.wtv;*.wv;*.wve;*.x11grab;*.xa;*.xbin;*.xmv;*.xv;*.xvag;*.xwma;*.yop;*.yuv4mpegpipe"; /// 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 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 /// Default constructor. public MainWindow() { this.ButterflowWrapper.ParsedConsoleOutputRecieved += ButterflowWrapper_ParsedConsoleOutputRecieved; this.OptionsConfiguration.AddConstantCallProperty("CommandLineOutput"); InitializeComponent(); // Check for updates. if (OctokitWrapper.CurrentVersionStatus == OctokitWrapper.VersionStatus.Behind) { var updateMessageBoxResult = MessageBox.Show(string.Format("{0} {1}", Localization.Localization.BehindVersionStatusDescription, Localization.Localization.BehindVersionQuestion), Localization.Localization.UpdateAvailableLabel, MessageBoxButton.YesNo, MessageBoxImage.Information); // If the user wants to update now, take them to the latest release on github and close this window. if (updateMessageBoxResult == MessageBoxResult.Yes) { Process.Start("https://github.com/wagesj45/butterflow-ui/releases/latest"); this.Close(); } } } #region Methods /// Gets the recursive children of a element that are of type . /// Generic type parameter. /// The parent element. /// /// An enumerator that allows foreach to be used to process the recursive childrens in this /// collection. /// private IEnumerable GetRecursiveChildren(object parent) where T : DependencyObject { if (parent is DependencyObject) { var list = new List(); foreach (var child in LogicalTreeHelper.GetChildren((DependencyObject)parent)) { if (child is DependencyObject) { if (child is T) { list.Add((T)child); } list.AddRange(GetRecursiveChildren((DependencyObject)child)); } } return list; } return Enumerable.Empty(); } /// Butterflow wrapper parsed console output recieved. /// Source of the event. /// The ButterflowOutputArgs to process. private void ButterflowWrapper_ParsedConsoleOutputRecieved(object sender, ButterflowWrapper.ButterflowOutputArgs e) { switch (e.OutputType) { case ButterflowWrapper.ButterflowOutputType.Width: this.OptionsConfiguration.Width = e.Value; break; case ButterflowWrapper.ButterflowOutputType.Height: this.OptionsConfiguration.Height = e.Value; break; case ButterflowWrapper.ButterflowOutputType.Progress: // This case doesn't need to be considered since we're binding the progress bar's value to a property on the butterflow wrapper. // We may use this in the future, though. default: break; } } /// Event handler. Called by btnFilePicker for click events. /// Source of the event. /// Routed event information. private void btnFilePicker_Click(object sender, RoutedEventArgs e) { var ofd = new OpenFileDialog(); ofd.Multiselect = true; ofd.Filter = Localization.Localization.SupportedFileTypesLabel + "|" + SUPPORTED_FILE_TYPES; var result = ofd.ShowDialog(this); if (result.HasValue && result.Value) { this.OptionsConfiguration.VideoInput = ofd.FileNames; this.ButterflowWrapper.Probe(ofd.FileName); if (ofd.FileNames.Count() == 1) { this.txtFileName.Text = ofd.FileName; this.mediaPreview.Source = new Uri(ofd.FileName); //Hack to get the first frame to display in the media preview element. this.mediaPreview.Play(); this.mediaPreview.PausePlayback(); } else { this.txtFileName.Text = Localization.Localization.MultipleFilesText; this.mediaPreview.Source = default(Uri); } } } /// Event handler. Called by btnFileOutputPicker for click events. /// Source of the event. /// Routed event information. private void btnFileOutputPicker_Click(object sender, RoutedEventArgs e) { var sfd = new SaveFileDialog(); sfd.Filter = "MPEG 4|*.mp4"; var result = sfd.ShowDialog(this); if (result.HasValue && result.Value) { this.OptionsConfiguration.VideoOutput = sfd.FileName; } } /// Event handler. Called by PlaybackRate radio buttons for checked events. /// Source of the event. /// Routed event information. private void PlaybackRateRadioButton_Checked(object sender, RoutedEventArgs e) { var typedSender = (RadioButton)sender; if (typedSender != null) { var tag = typedSender.Tag.ToString(); this.OptionsConfiguration.PlaybackRate = tag; } } /// Event handler. Called by bntVideoPlay for click events. /// Source of the event. /// Routed event information. private void bntVideoPlay_Click(object sender, RoutedEventArgs e) { if (this.mediaPreview.CanPlay() || this.mediaPreview.CanPause.GetValueOrDefault(false)) { this.mediaPreview.TogglePlayPause(); } if (this.mediaPreview.IsPlaying) { this.PlayPauseButtonIcon.Template = Application.Current.Resources["PauseIcon"] as ControlTemplate; } else { this.PlayPauseButtonIcon.Template = Application.Current.Resources["PlayIcon"] as ControlTemplate; } } /// Event handler. Called by bntVideoStop for click events. /// Source of the event. /// Routed event information. private void bntVideoStop_Click(object sender, RoutedEventArgs e) { this.PlayPauseButtonIcon.Template = Application.Current.Resources["PlayIcon"] as ControlTemplate; this.mediaPreview.Stop(); } /// Event handler. Called by bntVideoForward for click events. /// Source of the event. /// Routed event information. private void bntVideoForward_Click(object sender, RoutedEventArgs e) { if (this.mediaPreview.CanSkipForward(null)) { this.mediaPreview.SkipForward(null); } } /// Event handler. Called by bntVideoBackward for click events. /// Source of the event. /// Routed event information. private void bntVideoBackward_Click(object sender, RoutedEventArgs e) { if (this.mediaPreview.CanSkipBack(null)) { this.mediaPreview.SkipBack(null); } } /// 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 ended events. /// Source of the event. /// Routed event information. private void mediaPreview_MediaEnded(object sender, RoutedEventArgs e) { 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(); } } } /// Event handler. Called by txtPlaybackRate for got focus events. /// Source of the event. /// Routed event information. private void txtPlaybackRate_GotFocus(object sender, RoutedEventArgs e) { //Clear all the radio buttons because we got focus from the user in the playbackrate textbox. var playbackRateRadioButtons = GetRecursiveChildren(this.butterflowUIWindow); foreach (var radioButton in playbackRateRadioButtons) { radioButton.IsChecked = false; } } /// Event handler. Called by btnCopyArguments for click events. /// Source of the event. /// Routed event information. private void btnCopyArguments_Click(object sender, RoutedEventArgs e) { Clipboard.SetText(this.OptionsConfiguration.CommandLineOutput); } /// Event handler. Called by btnProcess for click events. /// Source of the event. /// Routed event information. private void btnProcess_Click(object sender, RoutedEventArgs e) { this.ButterflowWrapper.Run(this.OptionsConfiguration); } /// Event handler. Called by btnCancel for click events. /// Source of the event. /// Routed event information. private void btnCancel_Click(object sender, RoutedEventArgs e) { this.ButterflowWrapper.Cancel(); } /// Event handler. Called by btnRemoveSubregion for click events. /// Source of the event. /// Routed event information. private void btnRemoveSubregion_Click(object sender, RoutedEventArgs e) { var button = sender as Button; if (button != null) { if (button.Tag.GetType() == typeof(Guid)) { var identifier = (Guid)button.Tag; if (this.OptionsConfiguration.Subregions.Any(sr => sr.Identifier == identifier)) { var item = this.OptionsConfiguration.Subregions.Where(sr => sr.Identifier == identifier).First(); this.OptionsConfiguration.Subregions.Remove(item); } } } } /// Event handler. Called by menuOpen for click events. /// Source of the event. /// Routed event information. private void menuOpen_Click(object sender, RoutedEventArgs e) { var ofd = new OpenFileDialog(); ofd.Filter = Localization.Localization.ButterflowUIConfigurationLabel + "|*.bui"; var result = ofd.ShowDialog(this); if (result.HasValue && result.Value) { var binaryFormatter = new BinaryFormatter(); var file = binaryFormatter.Deserialize(ofd.OpenFile()); if (file is OptionsConfigurationFile) { this.OptionsConfiguration.LoadFile((OptionsConfigurationFile)file); } } } /// Event handler. Called by menuSaveConfiguration for click events. /// Source of the event. /// Routed event information. private void menuSaveConfiguration_Click(object sender, RoutedEventArgs e) { var sfd = new SaveFileDialog(); sfd.Filter = Localization.Localization.ButterflowUIConfigurationLabel + "|*.bui"; var result = sfd.ShowDialog(this); if (result.HasValue && result.Value) { var binaryFormatter = new BinaryFormatter(); binaryFormatter.Serialize(sfd.OpenFile(), this.OptionsConfiguration.ToFile()); } } /// Event handler. Called by menuButterflowGithub for click events. /// Source of the event. /// Routed event information. private void menuButterflowGithub_Click(object sender, RoutedEventArgs e) { Process.Start("https://github.com/dthpham/butterflow"); } /// Event handler. Called by menuButterflowUIGithub for click events. /// Source of the event. /// Routed event information. private void menuButterflowUIGithub_Click(object sender, RoutedEventArgs e) { Process.Start("https://github.com/wagesj45/butterflow-ui"); } /// Event handler. Called by menuAboutButterflowUI for click events. /// Source of the event. /// Routed event information. private void menuAboutButterflowUI_Click(object sender, RoutedEventArgs e) { var aboutWindow = new AboutWindow(); aboutWindow.Show(); } /// Event handler. Called by menuOptions for click events. /// Source of the event. /// Routed event information. private void menuOptions_Click(object sender, RoutedEventArgs e) { var optionsWindow = new OptionsWindow(); optionsWindow.ShowDialog(); //Force an update on the command line arguments in case the user changed something in the //Options window that necessitates it, such as the compute device. this.OptionsConfiguration.ForceUpdate(); } #endregion } }