Better console UI

This commit is contained in:
Alexander Shabarshov 2026-05-12 07:38:07 +01:00
parent a6b9d9c069
commit ccfa3936c7
4 changed files with 105 additions and 33 deletions

106
Logger.cs
View File

@ -17,9 +17,16 @@ public static class Logger
} }
else else
{ {
Console.SetCursorPosition(0, _logLines);
Console.ForegroundColor = ConsoleColor.Cyan;
Console.Write($"{prefix} ");
Console.ForegroundColor = color; Console.ForegroundColor = color;
Console.WriteLine($"{prefix} {msg}"); Console.WriteLine(msg);
Console.ResetColor(); Console.ResetColor();
_logLines++; _logLines++;
} }
} }
@ -30,39 +37,88 @@ public static class Logger
public static void LogWarn(string msg) => Log("[WARN]", ConsoleColor.Yellow, msg); 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 LogError(string msg) => Log("[ERR ]", ConsoleColor.Red, msg);
public static void DrawProgress(int progressLevel, double progress, TimeSpan eta, double speed) private static readonly Dictionary<int, int> _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) if (PlainText || progressLevel < 0)
return; return;
lock (_consoleLock) lock (_consoleLock)
{ {
var width = Math.Max(20, Console.WindowWidth - 20); var barLine = _logLines + 1 + progressLevel * 2;
var filled = (int)(progress * width); var infoLine = _logLines + 2 + progressLevel * 2;
if (filled < 0) filled = 0;
if (filled > width) filled = width;
var barLine = _logLines + 1 + progressLevel*2; // Clear bar line
var infoLine = _logLines + 2 + progressLevel*2;
// Progress bar with 24-bit color (green)
Console.SetCursorPosition(0, barLine); Console.SetCursorPosition(0, barLine);
Console.Write("\u001b[38;2;0;255;0m["); Console.Write(new string(' ', Console.WindowWidth - 1));
Console.Write(new string('#', filled));
Console.Write(new string('-', width - filled));
Console.Write("]\u001b[0m");
// Info line: percentage, ETA, speed // Clear info line
Console.SetCursorPosition(0, infoLine); Console.SetCursorPosition(0, infoLine);
var etaStr = eta.TotalSeconds < 0 || double.IsInfinity(eta.TotalSeconds) Console.Write(new string(' ', Console.WindowWidth - 1));
? "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");
}
}
} }

View File

@ -15,6 +15,9 @@ public abstract class LoggingBase(int progressLine)
protected void LogError(string message) protected void LogError(string message)
=> Logger.LogError(message); => Logger.LogError(message);
protected void DrawProgress(double percent, TimeSpan eta, double fps) protected void DrawProgress(string name, double percent, TimeSpan eta, double fps)
=> Logger.DrawProgress(progressLine, percent, eta, fps); => Logger.DrawProgress(name, progressLine, percent, eta, fps);
protected void ClearProgress(int progressLevel)
=> Logger.ClearProgress(progressLevel);
} }

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.Text; using System.Text;
using FFmpeg.AutoGen;
namespace splitter; 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."); 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(); 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(); var sw = Stopwatch.StartNew();
@ -76,7 +85,7 @@ public class SimpleSplitter(int segmentNo) : LoggingBase(segmentNo), ISegmentPro
var etaSeconds = speed > 0 ? remaining / speed : remaining; var etaSeconds = speed > 0 ? remaining / speed : remaining;
var eta = TimeSpan.FromSeconds(etaSeconds); var eta = TimeSpan.FromSeconds(etaSeconds);
DrawProgress(progress, eta, speed); DrawProgress(name, progress, eta, speed);
} }
} }

View File

@ -42,6 +42,7 @@ public class TrackingSplitter : LoggingBase, ISegmentProcessor, IDisposable
if (!capture.IsOpened()) if (!capture.IsOpened())
throw new Exception("Cannot open video"); throw new Exception("Cannot open video");
var name = Path.GetFileNameWithoutExtension(outputFile);
var skip = TimeSpan.FromSeconds(start); var skip = TimeSpan.FromSeconds(start);
var duration = TimeSpan.FromSeconds(length); var duration = TimeSpan.FromSeconds(length);
@ -158,17 +159,20 @@ public class TrackingSplitter : LoggingBase, ISegmentProcessor, IDisposable
var etaSeconds = speed > 0 ? remainingFrames / speed : 0; var etaSeconds = speed > 0 ? remainingFrames / speed : 0;
var eta = TimeSpan.FromSeconds(etaSeconds); var eta = TimeSpan.FromSeconds(etaSeconds);
DrawProgress(progress, eta, speed); DrawProgress(name, progress, eta, speed);
} }
stdin.Flush(); stdin.Flush();
stdin.Close(); stdin.Close();
await ffmpeg.WaitForExitAsync(); await ffmpeg.WaitForExitAsync();
ClearProgress(_segmentNo);
if (ffmpeg.ExitCode != 0) if (ffmpeg.ExitCode != 0)
LogError($"Segment {_segmentNo} FFmpeg encoding failed"); LogError($"Segment {name} FFmpeg encoding failed");
else else
LogInfo($"Segment {_segmentNo} processing completed"); LogInfo($"Segment {name} processing completed");
} }
private (Rect box, Point2f center)? SelectTrackedObject( private (Rect box, Point2f center)? SelectTrackedObject(