public static void RecordScreenshot(MainWindow game) { var resolution = new Size(Settings.ScreenshotWidth, Settings.ScreenshotHeight); var oldsize = game.RenderSize; var oldZoomMultiplier = Settings.ZoomMultiplier; var oldHitTest = Settings.Editor.HitTest; if (Settings.Recording.ResIndZoom) { Settings.ZoomMultiplier *= game.ClientSize.Width > game.ClientSize.Height * 16 / 9 ? (float)Settings.ScreenshotWidth / (float)game.ClientSize.Width : (float)Settings.ScreenshotHeight / (float)game.ClientSize.Height; } Settings.Editor.HitTest = Settings.Recording.ShowHitTest; game.Canvas.Scale = Settings.ZoomMultiplier / oldZoomMultiplier; //Divide just in case anyone modifies the zoom multiplier to not be 1 using (var trk = game.Track.CreateTrackReader()) { RecordingScreenshot = true; game.Canvas.SetCanvasSize(game.RenderSize.Width, game.RenderSize.Height); game.Canvas.Layout(); var frontbuffer = SafeFrameBuffer.GenFramebuffer(); SafeFrameBuffer.BindFramebuffer(FramebufferTarget.Framebuffer, frontbuffer); var rbo2 = SafeFrameBuffer.GenRenderbuffer(); SafeFrameBuffer.BindRenderbuffer(RenderbufferTarget.Renderbuffer, rbo2); SafeFrameBuffer.RenderbufferStorage(RenderbufferTarget.Renderbuffer, RenderbufferStorage.Rgb8, resolution.Width, resolution.Height); SafeFrameBuffer.FramebufferRenderbuffer(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, RenderbufferTarget.Renderbuffer, rbo2); SafeFrameBuffer.BindRenderbuffer(RenderbufferTarget.Renderbuffer, 0); _screenshotbuffer = new byte[game.RenderSize.Width * game.RenderSize.Height * 3];// 3 bytes per pixel game.Title = Program.WindowTitle + " [Capturing Screenshot]"; game.ProcessEvents(); string filename; if (game.Track.Name == Constants.DefaultTrackName) { filename = Program.UserDirectory + "Untitled Track" + ".png"; } else { filename = Program.UserDirectory + game.Track.Name + ".png"; } var recmodesave = Settings.Local.RecordingMode; Settings.Local.RecordingMode = true; game.Render(); var screenshotframe = GrabScreenshot(game, frontbuffer, true); SaveScreenshot(game.RenderSize.Width, game.RenderSize.Height, screenshotframe, filename); SafeFrameBuffer.BindFramebuffer(FramebufferTarget.Framebuffer, 0); //Delete the FBOs SafeFrameBuffer.DeleteFramebuffer(frontbuffer); SafeFrameBuffer.DeleteRenderbuffers(1, new[] { rbo2 }); RecordingScreenshot = false; game.RenderSize = oldsize; Settings.ZoomMultiplier = oldZoomMultiplier; game.Title = Program.WindowTitle; Settings.Local.RecordingMode = recmodesave; Settings.Editor.HitTest = oldHitTest; game.Canvas.SetSize(game.RenderSize.Width, game.RenderSize.Height); game.Canvas.Scale = 1.0f; _screenshotbuffer = null; } }
public static void RecordTrack(MainWindow game, bool is1080P, bool smooth, bool music) { var flag = game.Track.GetFlag(); if (flag == null) { return; } var resolution = new Size(is1080P ? 1920 : 1280, is1080P ? 1080 : 720); var oldsize = game.RenderSize; var invalid = false; using (var trk = game.Track.CreateTrackReader()) { Recording = true; game.Track.Reset(); Recording1080p = is1080P; //Set colors back to default for triggers linerider.Utils.Constants.TriggerBGColor = new Color4((byte)game.Track.StartingBGColorR, (byte)game.Track.StartingBGColorG, (byte)game.Track.StartingBGColorB, (byte)255); linerider.Utils.Constants.StaticTriggerBGColor = new Color4((byte)game.Track.StartingBGColorR, (byte)game.Track.StartingBGColorG, (byte)game.Track.StartingBGColorB, (byte)255); linerider.Utils.Constants.StaticTriggerLineColorChange = Color.FromArgb(255, game.Track.StartingLineColorR, game.Track.StartingLineColorG, game.Track.StartingLineColorB); linerider.Utils.Constants.TriggerLineColorChange = Color.FromArgb(255, game.Track.StartingLineColorR, game.Track.StartingLineColorG, game.Track.StartingLineColorB); var state = game.Track.GetStart(); var frame = flag.FrameID; game.Canvas.SetCanvasSize(game.RenderSize.Width, game.RenderSize.Height); game.Canvas.Layout(); if (frame > 400) //many frames, will likely lag the game. Update the window as a fallback. { game.Title = Program.WindowTitle + " [Validating flag]"; game.ProcessEvents(); } for (var i = 0; i < frame; i++) { state = trk.TickBasic(state); } for (var i = 0; i < state.Body.Length; i++) { if (state.Body[i].Location != flag.State.Body[i].Location || state.Body[i].Previous != flag.State.Body[i].Previous) { invalid = true; break; } } var frontbuffer = SafeFrameBuffer.GenFramebuffer(); SafeFrameBuffer.BindFramebuffer(FramebufferTarget.Framebuffer, frontbuffer); var rbo2 = SafeFrameBuffer.GenRenderbuffer(); SafeFrameBuffer.BindRenderbuffer(RenderbufferTarget.Renderbuffer, rbo2); SafeFrameBuffer.RenderbufferStorage(RenderbufferTarget.Renderbuffer, RenderbufferStorage.Rgb8, resolution.Width, resolution.Height); SafeFrameBuffer.FramebufferRenderbuffer(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, RenderbufferTarget.Renderbuffer, rbo2); SafeFrameBuffer.BindRenderbuffer(RenderbufferTarget.Renderbuffer, 0); if (!invalid) { _screenshotbuffer = new byte[game.RenderSize.Width * game.RenderSize.Height * 3];// 3 bytes per pixel string errormessage = "An unknown error occured during recording."; game.Title = Program.WindowTitle + " [Recording | Hold ESC to cancel]"; game.ProcessEvents(); var filename = Program.UserDirectory + game.Track.Name + ".mp4"; var flagbackup = flag; var hardexit = false; var recmodesave = Settings.Local.RecordingMode; Settings.Local.RecordingMode = true; game.Track.StartIgnoreFlag(); game.Render(); var dir = Program.UserDirectory + game.Track.Name + "_rec"; if (!Directory.Exists(dir)) { Directory.CreateDirectory(dir); } var firstframe = GrabScreenshot(game, frontbuffer); SaveScreenshot(game.RenderSize.Width, game.RenderSize.Height, firstframe, dir + Path.DirectorySeparatorChar + "tmp" + 0 + ".png"); int framecount = smooth ? ((frame + 1) * 60) / 40 : frame + 1; //Add a extra frame double frametime = 0; Stopwatch sw = Stopwatch.StartNew(); for (var i = 0; i < framecount; i++) { if (hardexit) { break; } if (smooth) { var oldspot = frametime; frametime += 40f / 60f; if (i == 0) { //bugfix: //frame blending uses the previous frame. //so the first frame would be recorded twice, //instead of blended game.Track.Update(1); } else if ((int)frametime != (int)oldspot) { game.Track.Update(1); } var blend = frametime - Math.Truncate(frametime); game.Render((float)blend); } else { game.Track.Update(1); game.Render(); } try { var screenshot = GrabScreenshot(game, frontbuffer); SaveScreenshot(game.RenderSize.Width, game.RenderSize.Height, screenshot, dir + Path.DirectorySeparatorChar + "tmp" + (i + 1) + ".png"); } catch { hardexit = true; errormessage = "An error occured when saving the frame."; } if (Keyboard.GetState()[Key.Escape]) { hardexit = true; errormessage = "The user manually cancelled recording."; } if (sw.ElapsedMilliseconds > 500) { game.Title = string.Format("{0} [Recording {1:P} | Hold ESC to cancel]", Program.WindowTitle, i / (double)framecount); game.ProcessEvents(); } } game.ProcessEvents(); if (!hardexit) { var parameters = new FFMPEGParameters(); parameters.AddOption("framerate", smooth ? "60" : "40"); parameters.AddOption("i", "\"" + dir + Path.DirectorySeparatorChar + "tmp%d.png" + "\""); if (music && !string.IsNullOrEmpty(game.Track.Song.Location) && game.Track.Song.Enabled) { var fn = Program.UserDirectory + "Songs" + Path.DirectorySeparatorChar + game.Track.Song.Location; parameters.AddOption("ss", game.Track.Song.Offset.ToString(Program.Culture)); parameters.AddOption("i", "\"" + fn + "\""); parameters.AddOption("c:a", "aac"); } double duration = framecount / (smooth ? 60.0 : 40.0); parameters.AddOption("t", duration.ToString(Program.Culture)); parameters.AddOption("vf", "vflip");//we save images upside down expecting ffmpeg to flip more efficiently. // ffmpeg x264 encoding doc: // https://trac.ffmpeg.org/wiki/Encode/H.264 parameters.AddOption("c:v", "libx264"); // we don't care _too_ much about filesize parameters.AddOption("preset", "fast"); parameters.AddOption("crf", "17"); // increase player compatibility: parameters.AddOption("pix_fmt", "yuv420p"); // this optimizes the encoding for animation // how well lr fits into that category i'm not sure. parameters.AddOption("tune", "animation"); parameters.OutputFilePath = filename; var failed = false; if (File.Exists(filename)) { try { File.Delete(filename); } catch { Program.NonFatalError("A file with the name " + game.Track.Name + ".mp4 already exists"); failed = true; errormessage = "Cannot replace a file of the existing name " + game.Track.Name + ".mp4."; } } if (!failed) { game.Title = Program.WindowTitle + " [Encoding Video | 0%]"; game.ProcessEvents(); try { FFMPEG.Execute(parameters, (string s) => { int idx = s.IndexOf("frame=", StringComparison.InvariantCulture); if (idx != -1) { idx += "frame=".Length; for (; idx < s.Length; idx++) { if (char.IsNumber(s[idx])) { break; } } var space = s.IndexOf(" ", idx, StringComparison.InvariantCulture); if (space != -1) { var sub = s.Substring(idx, space - idx); var parsedint = -1; if (int.TryParse(sub, out parsedint)) { game.Title = Program.WindowTitle + string.Format(" [Encoding Video | {0:P} | Hold ESC to cancel]", parsedint / (double)framecount); game.ProcessEvents(); if (Keyboard.GetState()[Key.Escape]) { hardexit = true; errormessage = "The user manually cancelled recording."; return(false); } } } } return(true); }); } catch (Exception e) { linerider.Utils.ErrorLog.WriteLine( "ffmpeg error" + Environment.NewLine + e); hardexit = true; errormessage = "An ffmpeg error occured.\n" + e.Message; } } } try { Directory.Delete(dir, true); } catch { Program.NonFatalError("Unable to delete " + dir); } if (hardexit) { try { File.Delete(filename); } catch { Program.NonFatalError("Unable to delete " + filename); } } Settings.Local.RecordingMode = recmodesave; game.Title = Program.WindowTitle; game.Track.Stop(); game.ProcessEvents(); var openwindows = game.Canvas.GetOpenWindows(); foreach (var window in openwindows) { var w = window as WindowControl; w?.Close(); } if (File.Exists(filename)) { AudioService.Beep(); } else { game.Canvas.ShowError(errormessage); } } SafeFrameBuffer.BindFramebuffer(FramebufferTarget.Framebuffer, 0); SafeFrameBuffer.DeleteFramebuffer(frontbuffer); SafeFrameBuffer.DeleteRenderbuffers(1, new[] { rbo2 }); game.RenderSize = oldsize; Recording = false; game.Canvas.SetSize(game.RenderSize.Width, game.RenderSize.Height); _screenshotbuffer = null; } }
public static void RecordTrack(GLWindow game, bool is1080P) { var flag = game.Track.GetFlag(); if (flag == null) { return; } var resolution = new Size(is1080P ? 1920 : 1280, is1080P ? 1080 : 720); var oldsize = game.RenderSize; var invalid = false; var state = new Rider(); game.Track.Reset(state); var frame = flag.Frame; Recording = true; Recording1080p = is1080P; game.Canvas.SetSize(game.RenderSize.Width, game.RenderSize.Height); game.Canvas.FindChildByName("buttons").Position(Pos.CenterH); if (frame > 400) //many frames, will likely lag the game. Update the window as a fallback. { if (frame > (20 * (60 * 40))) //too many frames, could lag the game very bad. { return; } game.Title = Program.WindowTitle + " [Validating flag]"; game.ProcessEvents(); } for (var i = 0; i < frame; i++) { game.Track.Tick(state); } for (var i = 0; i < state.ModelAnchors.Length; i++) { if (state.ModelAnchors[i].Position != flag.State.ModelAnchors[i].Position || state.ModelAnchors[i].Prev != flag.State.ModelAnchors[i].Prev) { invalid = true; break; } } var frontbuffer = SafeFrameBuffer.GenFramebuffer(); SafeFrameBuffer.BindFramebuffer(FramebufferTarget.Framebuffer, frontbuffer); var rbo2 = SafeFrameBuffer.GenRenderbuffer(); SafeFrameBuffer.BindRenderbuffer(RenderbufferTarget.Renderbuffer, rbo2); SafeFrameBuffer.RenderbufferStorage(RenderbufferTarget.Renderbuffer, RenderbufferStorage.Rgb8, resolution.Width, resolution.Height); SafeFrameBuffer.FramebufferRenderbuffer(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, RenderbufferTarget.Renderbuffer, rbo2); SafeFrameBuffer.BindRenderbuffer(RenderbufferTarget.Renderbuffer, 0); if (!invalid) { string errormessage = "An unknown error occured during recording."; game.Title = Program.WindowTitle + " [Recording | Hold ESC to cancel]"; game.ProcessEvents(); var filename = Program.CurrentDirectory + game.Track.Name + ".mp4"; var flagbackup = flag; var hardexit = false; game.Track.Flag(); var recmodesave = game.SettingRecordingMode; game.SettingRecordingMode = true; game.Track.Start(true, true, false, false); game.Render(); var dir = Program.CurrentDirectory + game.Track.Name + "_rec"; if (!Directory.Exists(dir)) { Directory.CreateDirectory(dir); } var firstframe = GrabScreenshot(game, frontbuffer); SaveScreenshot(game.RenderSize.Width, game.RenderSize.Height, firstframe, dir + Path.DirectorySeparatorChar + "tmp" + 0 + ".png"); int[] savethreads = { 0 }; for (var i = 0; i < frame; i++) { if (hardexit) { break; } game.Track.Update(1); game.Render(); var screenshot = GrabScreenshot(game, frontbuffer); var objtopass = new Tuple <byte[], int>(screenshot, i + 1); savethreads[0] += 1; var save = new Task(t => { var passed = (Tuple <byte[], int>)t; try { SaveScreenshot(game.RenderSize.Width, game.RenderSize.Height, passed.Item1, dir + Path.DirectorySeparatorChar + "tmp" + passed.Item2 + ".png"); } catch { hardexit = true; errormessage = "An error occured when saving the frame."; } finally { Interlocked.Decrement(ref savethreads[0]); } }, objtopass); save.Start(); if (Keyboard.GetState()[Key.Escape]) { hardexit = true; errormessage = "The user manually cancelled recording."; } if (i % 40 == 0) { game.Title = string.Format("{0} [Recording {1:P}% | Hold ESC to cancel]", Program.WindowTitle, i / (double)frame); game.ProcessEvents(); } } if (!hardexit) { var parameters = new FFMPEGParameters(); parameters.AddOption("framerate", "40"); parameters.AddOption("i", "\"" + dir + Path.DirectorySeparatorChar + "tmp%d.png" + "\""); parameters.AddOption("vf", "vflip");//we save images upside down expecting ffmpeg to flip more efficiently. parameters.AddOption("c:v", "libx264"); parameters.AddOption("preset", "veryfast"); parameters.AddOption("qp", "0"); // parameters.AddOption("scale",is1080p?"1920:1080":"1280:720"); parameters.OutputFilePath = filename; var failed = false; while (savethreads[0] != 0) { Thread.Sleep(1); } if (File.Exists(filename)) { try { File.Delete(filename); } catch { Program.NonFatalError("A file with the name " + game.Track.Name + ".mp4 already exists"); failed = true; errormessage = "Cannot replace a file of the existing name " + game.Track.Name + ".mp4."; } } if (!failed) { game.Title = Program.WindowTitle + " [Encoding Video | 0%]"; game.ProcessEvents(); try { FFMPEG.Execute(parameters, (string s) => { int idx = s.IndexOf("frame=", StringComparison.InvariantCulture); if (idx != -1) { idx += "frame=".Length; for (; idx < s.Length; idx++) { if (char.IsNumber(s[idx])) { break; } } var space = s.IndexOf(" ", idx, StringComparison.InvariantCulture); if (space != -1) { var sub = s.Substring(idx, space - idx); var parsedint = -1; if (int.TryParse(sub, out parsedint)) { game.Title = Program.WindowTitle + string.Format(" [Encoding Video | {0:P}% | Hold ESC to cancel]", parsedint / (double)frame); game.ProcessEvents(); if (Keyboard.GetState()[Key.Escape]) { hardexit = true; errormessage = "The user manually cancelled recording."; return(false); } } } } return(true); }); } catch (Exception e) { Program.NonFatalError("ffmpeg error.\r\n" + e); hardexit = true; errormessage = "An ffmpeg error occured."; } } } try { Directory.Delete(dir, true); } catch { Program.NonFatalError("Unable to delete " + dir); } if (hardexit) { try { File.Delete(filename); } catch { Program.NonFatalError("Unable to delete " + filename); } } game.SettingRecordingMode = recmodesave; game.Title = Program.WindowTitle; game.Track.RestoreFlag(flagbackup); game.Track.Stop(); game.ProcessEvents(); var openwindows = game.Canvas.GetOpenWindows(); foreach (var window in openwindows) { var w = window as WindowControl; w?.Close(); } if (File.Exists(filename)) { try { AudioPlayback.Init(); MemoryStream ms = new MemoryStream(GameResources.beep); SoundStream str = new SoundStream(ms); str.Play(0, 1); int count = 0; while (str.Playing) { Thread.Sleep(1); count += 1; if (count >= 3000)//in case something weird happens { break; } } str.Dispose(); ms.Dispose(); } catch { //ignored } } else { PopupWindow.Error(game.Canvas, game, errormessage, "Error!"); } } SafeFrameBuffer.BindFramebuffer(FramebufferTarget.Framebuffer, 0); SafeFrameBuffer.DeleteFramebuffer(frontbuffer); SafeFrameBuffer.DeleteRenderbuffers(1, new[] { rbo2 }); game.RenderSize = oldsize; Recording = false; game.Canvas.SetSize(game.RenderSize.Width, game.RenderSize.Height); game.Canvas.FindChildByName("buttons").Position(Pos.CenterH); }