From ccfa3936c70a9b34b7994a036fcb374950bb2663 Mon Sep 17 00:00:00 2001 From: unclshura Date: Tue, 12 May 2026 07:38:07 +0100 Subject: [PATCH] Better console UI --- Logger.cs | 104 ++++++++++++++++++++++++++++++++++---------- LoggingBase.cs | 7 ++- SimpleSplitter.cs | 15 +++++-- TrackingSplitter.cs | 12 +++-- 4 files changed, 105 insertions(+), 33 deletions(-) diff --git a/Logger.cs b/Logger.cs index e6673a5..96d16c6 100644 --- a/Logger.cs +++ b/Logger.cs @@ -17,9 +17,16 @@ public static class Logger } else { + Console.SetCursorPosition(0, _logLines); + + Console.ForegroundColor = ConsoleColor.Cyan; + Console.Write($"{prefix} "); + Console.ForegroundColor = color; - Console.WriteLine($"{prefix} {msg}"); + Console.WriteLine(msg); + Console.ResetColor(); + _logLines++; } } @@ -30,39 +37,88 @@ public static class Logger public static void LogWarn(string msg) => Log("[WARN]", ConsoleColor.Yellow, msg); public static void LogError(string msg) => Log("[ERR ]", ConsoleColor.Red, msg); - public static void DrawProgress(int progressLevel, double progress, TimeSpan eta, double speed) + private static readonly Dictionary _progressTrack = new(); + + public static void DrawProgress(string name, int progressLevel, double progress, TimeSpan eta, double speed) + { + if (PlainText || progressLevel < 0) + return; + + // Crop name to max 20 chars + name = name.Length > 20 ? name[..20] : name; + + lock (_consoleLock) + { + var width = Math.Max(20, Console.WindowWidth - 20); + + // Reserve space for name + space + var namePrefix = name + " "; + var barWidth = Math.Max(10, width - namePrefix.Length); + + var filled = (int)(progress * barWidth); + if (filled < 0) filled = 0; + if (filled > barWidth) filled = barWidth; + + // --- NEW: skip drawing if visually unchanged --- + if (_progressTrack.TryGetValue(progressLevel, out var lastFilled) && + lastFilled == filled) + { + return; // no visual change → skip + } + + _progressTrack[progressLevel] = filled; + // ------------------------------------------------ + + var barLine = _logLines + 1 + progressLevel * 2; + var infoLine = _logLines + 2 + progressLevel * 2; + + // Draw progress bar + Console.SetCursorPosition(0, barLine); + Console.Write("\u001b[38;2;0;255;0m"); // green + Console.Write(namePrefix); + Console.Write("["); + Console.Write(new string('#', filled)); + Console.Write(new string('-', barWidth - filled)); + Console.Write("]\u001b[0m"); + + // Info line + Console.SetCursorPosition(0, infoLine); + + var etaStr = eta.TotalSeconds < 0 || double.IsInfinity(eta.TotalSeconds) + ? "ETA: --:--" + : $"ETA: {eta:mm\\:ss}"; + + var speedStr = double.IsNaN(speed) || double.IsInfinity(speed) + ? "Speed: -.-x" + : $"Speed: {speed:F2}x"; + + var info = $"{progress * 100:0.0}% {etaStr} {speedStr} "; + + Console.Write("\u001b[38;2;180;180;180m" + + info.PadRight(Console.WindowWidth - 1) + + "\u001b[0m"); + } + } + + + public static void ClearProgress(int progressLevel) { if (PlainText || progressLevel < 0) return; lock (_consoleLock) { - var width = Math.Max(20, Console.WindowWidth - 20); - var filled = (int)(progress * width); - if (filled < 0) filled = 0; - if (filled > width) filled = width; + var barLine = _logLines + 1 + progressLevel * 2; + var infoLine = _logLines + 2 + progressLevel * 2; - var barLine = _logLines + 1 + progressLevel*2; - var infoLine = _logLines + 2 + progressLevel*2; - - // Progress bar with 24-bit color (green) + // Clear bar line Console.SetCursorPosition(0, barLine); - Console.Write("\u001b[38;2;0;255;0m["); - Console.Write(new string('#', filled)); - Console.Write(new string('-', width - filled)); - Console.Write("]\u001b[0m"); + Console.Write(new string(' ', Console.WindowWidth - 1)); - // Info line: percentage, ETA, speed + // Clear info line Console.SetCursorPosition(0, infoLine); - var etaStr = eta.TotalSeconds < 0 || double.IsInfinity(eta.TotalSeconds) - ? "ETA: --:--" - : $"ETA: {eta:mm\\:ss}"; - var speedStr = double.IsNaN(speed) || double.IsInfinity(speed) - ? "Speed: -.-x" - : $"Speed: {speed:F2}x"; - - var info = $"{progress * 100:0.0}% {etaStr} {speedStr} "; - Console.Write("\u001b[38;2;180;180;180m" + info.PadRight(Console.WindowWidth - 1) + "\u001b[0m"); + Console.Write(new string(' ', Console.WindowWidth - 1)); } } + } diff --git a/LoggingBase.cs b/LoggingBase.cs index 1edb6b9..cb94297 100644 --- a/LoggingBase.cs +++ b/LoggingBase.cs @@ -15,6 +15,9 @@ public abstract class LoggingBase(int progressLine) protected void LogError(string message) => Logger.LogError(message); - protected void DrawProgress(double percent, TimeSpan eta, double fps) - => Logger.DrawProgress(progressLine, percent, eta, fps); + protected void DrawProgress(string name, double percent, TimeSpan eta, double fps) + => Logger.DrawProgress(name, progressLine, percent, eta, fps); + + protected void ClearProgress(int progressLevel) + => Logger.ClearProgress(progressLevel); } diff --git a/SimpleSplitter.cs b/SimpleSplitter.cs index a589259..bf0cb20 100644 --- a/SimpleSplitter.cs +++ b/SimpleSplitter.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Text; +using FFmpeg.AutoGen; namespace splitter; @@ -39,12 +40,20 @@ public class SimpleSplitter(int segmentNo) : LoggingBase(segmentNo), ISegmentPro using var proc = Process.Start(psi) ?? throw new Exception("Failed to start ffmpeg."); - ShowFFMpegProgress(length, proc); + var name = Path.GetFileNameWithoutExtension(outputFile); + ShowFFMpegProgress(length, proc, name); proc.WaitForExit(); + + ClearProgress(segmentNo); + + if (proc.ExitCode != 0) + LogError($"Segment {name} FFmpeg encoding failed"); + else + LogInfo($"Segment {name} processing completed"); } - private void ShowFFMpegProgress(double length, Process proc) + private void ShowFFMpegProgress(double length, Process proc, string name) { var sw = Stopwatch.StartNew(); @@ -76,7 +85,7 @@ public class SimpleSplitter(int segmentNo) : LoggingBase(segmentNo), ISegmentPro var etaSeconds = speed > 0 ? remaining / speed : remaining; var eta = TimeSpan.FromSeconds(etaSeconds); - DrawProgress(progress, eta, speed); + DrawProgress(name, progress, eta, speed); } } diff --git a/TrackingSplitter.cs b/TrackingSplitter.cs index 1d07191..a517d75 100644 --- a/TrackingSplitter.cs +++ b/TrackingSplitter.cs @@ -42,7 +42,8 @@ public class TrackingSplitter : LoggingBase, ISegmentProcessor, IDisposable if (!capture.IsOpened()) throw new Exception("Cannot open video"); - var skip = TimeSpan.FromSeconds(start); + var name = Path.GetFileNameWithoutExtension(outputFile); + var skip = TimeSpan.FromSeconds(start); var duration = TimeSpan.FromSeconds(length); capture.Set(VideoCaptureProperties.PosMsec, start); @@ -158,17 +159,20 @@ public class TrackingSplitter : LoggingBase, ISegmentProcessor, IDisposable var etaSeconds = speed > 0 ? remainingFrames / speed : 0; var eta = TimeSpan.FromSeconds(etaSeconds); - DrawProgress(progress, eta, speed); + DrawProgress(name, progress, eta, speed); } stdin.Flush(); stdin.Close(); await ffmpeg.WaitForExitAsync(); + + ClearProgress(_segmentNo); + if (ffmpeg.ExitCode != 0) - LogError($"Segment {_segmentNo} FFmpeg encoding failed"); + LogError($"Segment {name} FFmpeg encoding failed"); else - LogInfo($"Segment {_segmentNo} processing completed"); + LogInfo($"Segment {name} processing completed"); } private (Rect box, Point2f center)? SelectTrackedObject(