public static List <Load> GetOverworldLoads(VideoCapture capture, float scale, float timeStart, float timeEnd, LoadType loadTypes, Action <LoadRemover.ProgressPhase, float> updateProgress) { List <Load> loads = new List <Load>(); var binocularFrames = FindBinocularFrames(capture, timeStart, timeEnd); List <int> startingFrames = new List <int>(); List <int> endingFrames = new List <int>(); int lastFrame = -2; foreach (var frame in binocularFrames) { if (frame > lastFrame + 1) { startingFrames.Add(frame); endingFrames.Add(lastFrame); } lastFrame = frame; } float fps = (float)capture.Fps; int progress = 0; if (loadTypes.HasFlag(LoadType.Overworld)) { foreach (var f in startingFrames) { updateProgress.Invoke(LoadRemover.ProgressPhase.Phase_6_OverworldLoads, 0.5f + ((progress++) / (float)startingFrames.Count) * 0.25f); // 30 fps -> 10 frozen frames // 60 fps -> 20 frozen frames Rect cropRect = new Rect(0, 0, (int)(capture.FrameWidth * 0.5f), capture.FrameHeight); var _loadStart = Util.CountFrozenFrames(LoadType.Overworld, capture, f / fps, (int)(fps / 3), (int)fps * 25, cropRect); if (!_loadStart.HasValue) { continue; } var loadStart = _loadStart.Value; // Check when the life counter first appears, skip three seconds for (int i = (int)fps * 3; i < fps * 20; i++) { var lifeCount = LifeCounter.GetLifeCount(capture, (f + i) / fps, scale); if (lifeCount >= 0) { loads.Add(new Load(LoadType.Overworld, loadStart.FrameStart, f + i)); break; } } } } updateProgress.Invoke(LoadRemover.ProgressPhase.Phase_6_OverworldLoads, 0.66f); progress = 0; if (loadTypes.HasFlag(LoadType.BackSign)) { foreach (var f in startingFrames) { updateProgress.Invoke(LoadRemover.ProgressPhase.Phase_6_OverworldLoads, 0.75f + ((progress++) / (float)startingFrames.Count) * 0.25f); // Check back sign loads var _backSignLoad = Util.CountFrozenFrames(LoadType.BackSign, capture, (f / fps) - backSignLoadMaxDuration, (int)fps * 1, (int)fps * backSignLoadMaxDuration); if (!_backSignLoad.HasValue) { continue; } loads.Add(_backSignLoad.Value); } } return(loads); }
public static LoadResults Start(string file, bool partialRun, CropSettings?crop, TrimSettings?trim, LoadType loadTypes, bool resize, Action <ProgressPhase, float> updateProgress) { List <Load> loads = new List <Load>(); updateProgress.Invoke(ProgressPhase.Phase_1_PreprocessVideo, 0); VideoCapture capture = new VideoCapture(file); string processedFile = CropTrimAndResizeVideo(capture, file, crop, /*trim,*/ resize); capture = VideoCapture.FromFile(processedFile); updateProgress.Invoke(ProgressPhase.Phase_2_StartingTime, 0); int startingFrame = 0; int endingFrame = trim.HasValue ? (int)Math.Min((trim.Value.End * capture.Fps) - 1, capture.FrameCount - 1) : capture.FrameCount - 1; if (!partialRun && loadTypes.HasFlag(LoadType.Start)) { var startLoad = Util.CountDarknessFrames(LoadType.Start, capture, trim?.Start ?? 0, (int)(capture.Fps * StartScreenMaxDuration)); if (startLoad.FrameStart == -1) { throw new Exception( "Start screen not detected, make sure the video starts on the \"Start\"/\"Options\" screen"); } loads.Add(startLoad); startingFrame = startLoad.FrameStart; if (loadTypes.HasFlag(LoadType.Overworld)) { var startOverworldLoad = Util.CountFrozenFrames(LoadType.Overworld, capture, startLoad.FrameEnd / capture.Fps, (int)capture.Fps / 5, (int)capture.Fps * 20); if (startOverworldLoad.HasValue) { loads.Add(startOverworldLoad.Value); } } } updateProgress.Invoke(ProgressPhase.Phase_3_VideoScale, 0); float videoScale = LifeCounter.GetLifeCountScale(capture, startingFrame, updateProgress); if (float.IsNaN(videoScale)) { throw new Exception("Video Scale couldn't be determined: " + videoScale); } updateProgress.Invoke(ProgressPhase.Phase_4_EndingTime, 0); if (!partialRun) { var _endingFrame = BossLoads.GetLastFinalBossFrame(capture, videoScale, endingFrame, updateProgress); if (!_endingFrame.HasValue) { throw new Exception( "Final hit not detected, make sure the video doesn't end more than 3 minutes after the final hit."); } endingFrame = _endingFrame.Value; } updateProgress.Invoke(ProgressPhase.Phase_5_EndSignLoads, 0); if (loadTypes.HasFlag(LoadType.EndSign)) { loads.AddRange(EndSignLoads.GetEndSignLoads(capture, videoScale, startingFrame, endingFrame, updateProgress)); } updateProgress.Invoke(ProgressPhase.Phase_6_OverworldLoads, 0); if (loadTypes.HasFlag(LoadType.Overworld) || loadTypes.HasFlag(LoadType.BackSign)) { loads.AddRange(OverworldLoads.GetOverworldLoads(capture, videoScale, startingFrame / (float)capture.Fps, endingFrame / (float)capture.Fps, loadTypes, updateProgress)); } updateProgress.Invoke(ProgressPhase.Phase_7_DeathLoads, 0); if (loadTypes.HasFlag(LoadType.Death)) { loads.AddRange(DeathLoads.GetDeathLoads(capture, videoScale, startingFrame, endingFrame, updateProgress)); } updateProgress.Invoke(ProgressPhase.Phase_8_BossLoads, 0); if (loadTypes.HasFlag(LoadType.Boss)) { loads.AddRange(BossLoads.GetBossLoads(capture, videoScale, startingFrame, endingFrame, updateProgress)); } int phase8Progress = 0; // Remove backsign loads that aren't preceded by an overworld load (ignore death loads for this) var sortedLoads = loads.OrderBy(l => l.FrameStart).ToList(); List <Load> backsignLoadsToRemove = new List <Load>(); for (int i = 0; i < sortedLoads.Count; i++) { if (sortedLoads[i].Type == LoadType.BackSign) { var bsLoad = sortedLoads[i]; for (int j = i - 1; j >= 0; j--) { var checkLoad = sortedLoads[j]; // only consider loads more than 3 seconds before the backsign load if (checkLoad.FrameStart > bsLoad.FrameStart - capture.Fps * 3.0) { continue; } if (checkLoad.Type == LoadType.Death) { continue; } if (checkLoad.Type == LoadType.Overworld) { break; } else { backsignLoadsToRemove.Add(bsLoad); } } } } foreach (var l in backsignLoadsToRemove) { loads.Remove(l); } // Remove unnecessary endsign loads (when they overlap with other loads) foreach (var load in loads.Where(l => l.Type != LoadType.EndSign).ToList()) { loads.RemoveAll(l => l.Type == LoadType.EndSign && l.Overlaps(load, (int)(capture.Fps * 0.5f))); } // Remove unnecessary backsign loads (when they overlap with other loads) foreach (var load in loads.Where(l => l.Type != LoadType.BackSign).ToList()) { loads.RemoveAll(l => l.Type == LoadType.BackSign && l.Overlaps(load, (int)(capture.Fps * 0.5f))); } // Remove all loads that start after the last frame loads.RemoveAll(l => l.FrameStart > endingFrame); updateProgress.Invoke(ProgressPhase.Phase_9_GenerateReport, 0); LoadResults results = new LoadResults(loads, (float)capture.Fps, startingFrame, endingFrame); results.SaveDebugImages(capture, "debugExport", "file"); var report = new LoadRemoverReport(Path.GetFileName(file), results, capture); var reportPath = Path.ChangeExtension(file, null) + "_report.html"; report.GenerateHtml(TemplateFile).Save(reportPath); updateProgress.Invoke(ProgressPhase.Phase_9_GenerateReport, 1); var openReport = MessageBox.Show($"Done! The report file can be found at {Environment.NewLine}{reportPath}{Environment.NewLine}" + $"Do you wish to open the report now?", "Report", MessageBoxButton.YesNo, MessageBoxImage.Question); if (openReport == MessageBoxResult.Yes) { // Open report in default application (hopefully the browser) var psi = new ProcessStartInfo { FileName = reportPath, UseShellExecute = true }; Process.Start(psi); } return(results); }