internal static async Task ConvertAsync(GifModel.Config config, IProgress progress) { Debug.Assert(IsAvailable()); var p = new Process { StartInfo = { UseShellExecute = false, WindowStyle = ProcessWindowStyle.Hidden, CreateNoWindow = true, RedirectStandardError = true, FileName = Path, Arguments = $"-framerate {config.FramesPerSecond} -i \"{config.TmpFilename}%4d.png\" -c:v libx264 -preset veryslow -crf 1 -pix_fmt yuv420p -frames:v {config.FramesPerSecond * config.NumSeconds} -r {config.FramesPerSecond} \"{config.Filename}\"" } }; var numFrames = config.NumSeconds * config.FramesPerSecond; progress.What = "converting"; // progress reports p.ErrorDataReceived += (sender, args) => { if (args.Data == null) { return; } if (args.Data.StartsWith("frame=")) { if (int.TryParse(args.Data.Substring("frame=".Length), out var frame)) { progress.Progress = frame / (float)numFrames; } } }; if (!p.Start()) { throw new Exception("could not start ffmpeg.exe"); } p.BeginErrorReadLine(); while (!p.HasExited) { await Task.Run(() => p.WaitForExit(100)); if (progress.Token.IsCancellationRequested && !p.HasExited) { p.Kill(); progress.Token.ThrowIfCancellationRequested(); } } }
public override async void Execute() { if (models.NumEnabled != 2) { models.Window.ShowErrorDialog("Exactly two image equations should be visible for exporting"); return; } if (models.Images.ImageType != typeof(TextureArray2D)) { models.Window.ShowErrorDialog("Only 2D textures are supported"); return; } if (!FFMpeg.IsAvailable()) { if (models.Window.ShowYesNoDialog("ffmpeg is required for this feature. " + "Please download the ffmpeg binaries and place them in the ImageViewer root directory. " + "Open ffmpeg download page and ImageViewer root?", "download ffmpeg?")) { var root = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location); if (root != null) { System.Diagnostics.Process.Start(root); } System.Diagnostics.Process.Start("https://www.ffmpeg.org/download.html"); } return; } var ids = models.GetEnabledPipelines(); // images valid? var img1 = models.Pipelines[ids[0]].Image; var img2 = models.Pipelines[ids[1]].Image; if (img1 == null) { return; } if (img2 == null) { return; } // ReSharper disable once CompareOfFloatsByEqualityOperator if (models.Display.Multiplier != 1.0f) { models.Window.ShowInfoDialog("Export will ignore image multiplier"); } path.InitFromEquations(models); var sfd = new SaveFileDialog { Filter = "MPEG-4 (*.mp4)|*.mp4", InitialDirectory = path.Directory, FileName = path.Filename }; if (sfd.ShowDialog(models.Window.TopmostWindow) != true) { return; } path.UpdateFromFilename(sfd.FileName, updateExtension: false); viewModel.InitTitles(models); var dia = new GifExportDialog(viewModel); if (models.Window.ShowDialog(dia) != true) { return; } // get tmp directory var tmpDir = Path.Combine(Path.GetTempPath(), Path.GetFileNameWithoutExtension(Path.GetTempFileName())); System.IO.Directory.CreateDirectory(tmpDir); var tmpName = tmpDir + "\\frame"; // delete old file if it existed (otherwise ffmpeg will hang) System.IO.File.Delete(sfd.FileName); GifModel.Config config = new GifModel.Config { Filename = sfd.FileName, TmpFilename = tmpName, FramesPerSecond = viewModel.FramesPerSecond, SliderWidth = viewModel.SliderSize, NumSeconds = viewModel.TotalSeconds, Label1 = viewModel.Title1, Label2 = viewModel.Title2, Left = (TextureArray2D)img1, Right = (TextureArray2D)img2, Overlay = (TextureArray2D)models.Overlay.Overlay }; models.Gif.CreateGif(config, models.SharedModel); await models.Progress.WaitForTaskAsync(); // delete tmp directory try { System.IO.Directory.Delete(tmpDir, true); } catch (Exception) { // ignored } if (models.Progress.LastTaskCancelledByUser) { return; } if (!String.IsNullOrEmpty(models.Progress.LastError)) { models.Window.ShowErrorDialog(models.Progress.LastError); } else if (askForVideo) { askForVideo = models.Window.ShowYesNoDialog("Open video?", "Finished exporting"); if (askForVideo) { System.Diagnostics.Process.Start(config.Filename); } } }
internal static async Task ConvertAsync(GifModel.Config config, IProgress progress) { Debug.Assert(IsAvailable()); string startArgs = $"-framerate {config.FramesPerSecond} -i \"{config.TmpFilename}%4d.png\" -c:v libx264 -preset veryslow -crf 12 -pix_fmt yuv420p -frames:v {config.FramesPerSecond * config.NumSeconds} -r {config.FramesPerSecond} \"{config.Filename}\""; var p = new Process { StartInfo = { UseShellExecute = false, WindowStyle = ProcessWindowStyle.Hidden, CreateNoWindow = true, RedirectStandardError = true, FileName = Path, Arguments = startArgs } }; var numFrames = config.NumSeconds * config.FramesPerSecond; progress.What = "converting"; // progress reports string errors = ""; p.ErrorDataReceived += (sender, args) => { if (args.Data == null) { return; } Console.Error.WriteLine("FFMPEG: " + args.Data); if (args.Data.StartsWith("frame=")) { var substr = args.Data.Substring("frame=".Length); substr = substr.TrimStart().Split(' ')[0]; if (int.TryParse(substr, out var frame)) { progress.Progress = frame / (float)numFrames; } } else if (args.Data.StartsWith("error", StringComparison.OrdinalIgnoreCase)) { errors += args.Data; errors += "\n"; } }; if (!p.Start()) { throw new Exception("could not start ffmpeg.exe"); } p.BeginErrorReadLine(); while (!p.HasExited) { await Task.Run(() => p.WaitForExit(100)); if (progress.Token.IsCancellationRequested && !p.HasExited) { p.Kill(); progress.Token.ThrowIfCancellationRequested(); } } if (!String.IsNullOrEmpty(errors)) { throw new Exception(errors); } }