mirror of
https://github.com/unclshura/splitter.git
synced 2026-06-22 00:22:01 +00:00
Spectre console UI added
This commit is contained in:
parent
14bcd31efe
commit
d0aade4fca
117
Logger.cs
117
Logger.cs
@ -1,117 +0,0 @@
|
|||||||
namespace splitter;
|
|
||||||
|
|
||||||
public class Logger(CommandLine cmd) : ILogger
|
|
||||||
{
|
|
||||||
int _logLines = Math.Max(1, Environment.ProcessorCount / 2) * 2;
|
|
||||||
readonly object _consoleLock = new();
|
|
||||||
|
|
||||||
public void Log(string prefix, ConsoleColor color, string msg)
|
|
||||||
{
|
|
||||||
lock (_consoleLock)
|
|
||||||
{
|
|
||||||
if (cmd.PlainText)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"{prefix} {msg}");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Console.SetCursorPosition(0, _logLines);
|
|
||||||
|
|
||||||
Console.ForegroundColor = ConsoleColor.Cyan;
|
|
||||||
Console.Write($"{prefix} ");
|
|
||||||
|
|
||||||
Console.ForegroundColor = color;
|
|
||||||
Console.WriteLine(msg);
|
|
||||||
|
|
||||||
Console.ResetColor();
|
|
||||||
|
|
||||||
_logLines++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly Dictionary<int, int> _progressTrack = new();
|
|
||||||
|
|
||||||
public void DrawProgress(string name, int progressLine, double progress, TimeSpan eta, double speed)
|
|
||||||
{
|
|
||||||
if (cmd.PlainText || progressLine < 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(progressLine, out var lastFilled) &&
|
|
||||||
lastFilled == filled)
|
|
||||||
{
|
|
||||||
return; // no visual change → skip
|
|
||||||
}
|
|
||||||
|
|
||||||
_progressTrack[progressLine] = filled;
|
|
||||||
// ------------------------------------------------
|
|
||||||
|
|
||||||
var barLine = _logLines + 1 + progressLine * 2;
|
|
||||||
var infoLine = _logLines + 2 + progressLine * 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 void ClearProgress(int progressLevel)
|
|
||||||
{
|
|
||||||
if (cmd.PlainText || progressLevel < 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
lock (_consoleLock)
|
|
||||||
{
|
|
||||||
var barLine = _logLines + 1 + progressLevel * 2;
|
|
||||||
var infoLine = _logLines + 2 + progressLevel * 2;
|
|
||||||
|
|
||||||
// Clear bar line
|
|
||||||
Console.SetCursorPosition(0, barLine);
|
|
||||||
Console.Write(new string(' ', Console.WindowWidth - 1));
|
|
||||||
|
|
||||||
// Clear info line
|
|
||||||
Console.SetCursorPosition(0, infoLine);
|
|
||||||
Console.Write(new string(' ', Console.WindowWidth - 1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
426
SpectreConsoleLogger.cs
Normal file
426
SpectreConsoleLogger.cs
Normal file
@ -0,0 +1,426 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Spectre.Console;
|
||||||
|
using Spectre.Console.Rendering;
|
||||||
|
|
||||||
|
namespace splitter;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Spectre.Console-based live TUI logger.
|
||||||
|
/// - Title centered at top of outer box
|
||||||
|
/// - Progress section (N rows) with name, gradient bar, %, ETA, FPS
|
||||||
|
/// - Log section taking remaining space, auto-scrolling to latest messages
|
||||||
|
/// - Bottom row with a [ Cancel ] button (dummy handler: key 'c' / 'C')
|
||||||
|
/// - Resizes with console window
|
||||||
|
/// </summary>
|
||||||
|
public sealed class SpectreConsoleLogger : ILogger, IDisposable
|
||||||
|
{
|
||||||
|
private readonly object _sync = new();
|
||||||
|
private readonly List<LogEntry> _logs = new();
|
||||||
|
private readonly Dictionary<int, ProgressEntry> _progress = new();
|
||||||
|
|
||||||
|
private readonly CancellationTokenSource _cts = new();
|
||||||
|
private Task? _uiTask;
|
||||||
|
private Task? _inputTask;
|
||||||
|
|
||||||
|
// Public configuration
|
||||||
|
public string Title { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Number of logical progress rows. UI reacts dynamically.
|
||||||
|
/// </summary>
|
||||||
|
public int NumberOfProcesses
|
||||||
|
{
|
||||||
|
get => _numberOfProcesses;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
lock (_sync)
|
||||||
|
{
|
||||||
|
_numberOfProcesses = Math.Max(1, value);
|
||||||
|
for (int i = 0; i < _numberOfProcesses; i++)
|
||||||
|
{
|
||||||
|
if (!_progress.ContainsKey(i))
|
||||||
|
_progress[i] = ProgressEntry.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int _numberOfProcesses = 1;
|
||||||
|
|
||||||
|
private const int MaxLogEntries = 500;
|
||||||
|
|
||||||
|
public SpectreConsoleLogger()
|
||||||
|
{
|
||||||
|
NumberOfProcesses = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- ILogger ----
|
||||||
|
|
||||||
|
public void ClearProgress(int progressLevel)
|
||||||
|
{
|
||||||
|
lock (_sync)
|
||||||
|
{
|
||||||
|
_progress[progressLevel] = ProgressEntry.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DrawProgress(string name, int progressLine, double progress, TimeSpan eta, double speed)
|
||||||
|
{
|
||||||
|
lock (_sync)
|
||||||
|
{
|
||||||
|
if (progressLine < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (progressLine >= NumberOfProcesses)
|
||||||
|
NumberOfProcesses = progressLine + 1;
|
||||||
|
|
||||||
|
_progress[progressLine] = new ProgressEntry(
|
||||||
|
name ?? string.Empty,
|
||||||
|
Math.Clamp(progress, 0.0, 1.0),
|
||||||
|
eta,
|
||||||
|
speed
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Log(string prefix, ConsoleColor color, string msg)
|
||||||
|
{
|
||||||
|
lock (_sync)
|
||||||
|
{
|
||||||
|
if (_logs.Count >= MaxLogEntries)
|
||||||
|
_logs.RemoveRange(0, _logs.Count - MaxLogEntries + 1);
|
||||||
|
|
||||||
|
_logs.Add(new LogEntry(
|
||||||
|
DateTime.Now,
|
||||||
|
prefix ?? string.Empty,
|
||||||
|
color,
|
||||||
|
msg ?? string.Empty
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- UI lifecycle ----
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Starts the live TUI loop. This method blocks until the cancellation token is triggered.
|
||||||
|
/// </summary>
|
||||||
|
public Task RunAsync(CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (_uiTask != null)
|
||||||
|
throw new InvalidOperationException("UI already started.");
|
||||||
|
|
||||||
|
var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(_cts.Token, cancellationToken);
|
||||||
|
var token = linkedCts.Token;
|
||||||
|
|
||||||
|
_uiTask = Task.Run(() => RunUiAsync(token), token);
|
||||||
|
_inputTask = Task.Run(() => RunInputLoopAsync(token), token);
|
||||||
|
|
||||||
|
return _uiTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RunUiAsync(CancellationToken token)
|
||||||
|
{
|
||||||
|
await AnsiConsole.Live(BuildRoot())
|
||||||
|
.AutoClear(false)
|
||||||
|
.StartAsync(async ctx =>
|
||||||
|
{
|
||||||
|
while (!token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
ctx.UpdateTarget(BuildRoot());
|
||||||
|
await Task.Delay(100, token);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RunInputLoopAsync(CancellationToken token)
|
||||||
|
{
|
||||||
|
// Dummy handler for Cancel button: press 'c' or 'C'
|
||||||
|
while (!token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
if (Console.KeyAvailable)
|
||||||
|
{
|
||||||
|
var key = Console.ReadKey(intercept: true);
|
||||||
|
if (key.KeyChar == 'c' || key.KeyChar == 'C')
|
||||||
|
{
|
||||||
|
((ILogger)this).LogWarn("Cancel button pressed (dummy handler).");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Delay(50, token).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Rendering ----
|
||||||
|
|
||||||
|
private IRenderable BuildRoot()
|
||||||
|
{
|
||||||
|
List<ProgressEntry> progressSnapshot;
|
||||||
|
List<LogEntry> logSnapshot;
|
||||||
|
string titleSnapshot;
|
||||||
|
int numberOfProcessesSnapshot;
|
||||||
|
|
||||||
|
lock (_sync)
|
||||||
|
{
|
||||||
|
titleSnapshot = Title;
|
||||||
|
numberOfProcessesSnapshot = NumberOfProcesses;
|
||||||
|
progressSnapshot = Enumerable.Range(0, numberOfProcessesSnapshot)
|
||||||
|
.Select(i => _progress.TryGetValue(i, out var p) ? p : ProgressEntry.Empty)
|
||||||
|
.ToList();
|
||||||
|
logSnapshot = _logs.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
var layout = new Layout("root")
|
||||||
|
.SplitRows(
|
||||||
|
new Layout("progress") { Size = Math.Max(3, numberOfProcessesSnapshot + 2) },
|
||||||
|
new Layout("log"),
|
||||||
|
new Layout("buttons") { Size = 3 }
|
||||||
|
);
|
||||||
|
|
||||||
|
layout["progress"].Update(BuildProgressPanel(progressSnapshot));
|
||||||
|
layout["log"].Update(BuildLogPanel(logSnapshot));
|
||||||
|
layout["buttons"].Update(BuildButtonsPanel());
|
||||||
|
return layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IRenderable BuildProgressPanel(IReadOnlyList<ProgressEntry> entries)
|
||||||
|
{
|
||||||
|
var table = new Table()
|
||||||
|
.Expand()
|
||||||
|
.Border(TableBorder.Rounded)
|
||||||
|
.AddColumn("[bold]Name[/]")
|
||||||
|
.AddColumn("[bold]Progress[/]")
|
||||||
|
.AddColumn("[bold]%[/]")
|
||||||
|
.AddColumn("[bold]ETA[/]")
|
||||||
|
.AddColumn("[bold]FPS[/]");
|
||||||
|
|
||||||
|
foreach (var entry in entries)
|
||||||
|
{
|
||||||
|
var bar = new DynamicGradientBar(entry.Progress);
|
||||||
|
|
||||||
|
var percentText = $"{entry.Progress * 100:0.0}%";
|
||||||
|
var etaText = entry.Eta == TimeSpan.Zero
|
||||||
|
? "--:--"
|
||||||
|
: $"{(int)entry.Eta.TotalMinutes:00}:{entry.Eta.Seconds:00}";
|
||||||
|
var fpsText = entry.Speed <= 0 ? "-" : $"{entry.Speed:0.0}";
|
||||||
|
|
||||||
|
table.AddRow(
|
||||||
|
new Markup(Escape(entry.Name)),
|
||||||
|
bar,
|
||||||
|
new Markup(percentText),
|
||||||
|
new Markup(etaText),
|
||||||
|
new Markup(fpsText)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return table;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IRenderable BuildLogPanel(IReadOnlyList<LogEntry> logs)
|
||||||
|
{
|
||||||
|
const int maxVisible = 200;
|
||||||
|
var slice = logs.Count > maxVisible
|
||||||
|
? logs.Skip(logs.Count - maxVisible).ToList()
|
||||||
|
: logs.ToList();
|
||||||
|
|
||||||
|
var rows = new List<IRenderable>();
|
||||||
|
|
||||||
|
foreach (var log in slice)
|
||||||
|
{
|
||||||
|
var time = log.Timestamp.ToString("HH:mm:ss");
|
||||||
|
var timeColor = "deepskyblue1"; // dark-ish blue
|
||||||
|
var prefixColor = "lightpink1"; // light magenta
|
||||||
|
var msgColor = MapConsoleColor(log.Color);
|
||||||
|
|
||||||
|
var line =
|
||||||
|
$"[{timeColor}]{Escape(time)}[/] " +
|
||||||
|
$"[{prefixColor}]{Escape(log.Prefix)}[/] " +
|
||||||
|
$"[{msgColor}]{Escape(log.Message)}[/]";
|
||||||
|
|
||||||
|
rows.Add(new Markup(line));
|
||||||
|
}
|
||||||
|
|
||||||
|
IRenderable content =
|
||||||
|
rows.Count == 0
|
||||||
|
? new Markup("[grey]No log messages yet.[/]")
|
||||||
|
: new Rows(rows);
|
||||||
|
|
||||||
|
var panel = new Panel(content)
|
||||||
|
{
|
||||||
|
Header = new PanelHeader("Log", Justify.Left),
|
||||||
|
Border = BoxBorder.Rounded,
|
||||||
|
Expand = true
|
||||||
|
};
|
||||||
|
|
||||||
|
return panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static IRenderable BuildButtonsPanel()
|
||||||
|
{
|
||||||
|
// Visual [ Cancel ] button; key handling is in RunInputLoopAsync
|
||||||
|
var text = new Markup("[bold white on red] Cancel [/]");
|
||||||
|
var grid = new Grid();
|
||||||
|
grid.AddColumn(new GridColumn().Centered());
|
||||||
|
grid.AddRow(text);
|
||||||
|
|
||||||
|
var panel = new Panel(grid)
|
||||||
|
{
|
||||||
|
Border = BoxBorder.Rounded,
|
||||||
|
Header = new PanelHeader("Actions", Justify.Left),
|
||||||
|
Expand = true
|
||||||
|
};
|
||||||
|
|
||||||
|
return panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Helpers ----
|
||||||
|
|
||||||
|
private static string Escape(string value) =>
|
||||||
|
value is null ? string.Empty : Markup.Escape(value);
|
||||||
|
|
||||||
|
private static string MapConsoleColor(ConsoleColor color) =>
|
||||||
|
color switch
|
||||||
|
{
|
||||||
|
ConsoleColor.Black => "black",
|
||||||
|
ConsoleColor.DarkBlue => "navy",
|
||||||
|
ConsoleColor.DarkGreen => "green",
|
||||||
|
ConsoleColor.DarkCyan => "teal",
|
||||||
|
ConsoleColor.DarkRed => "maroon",
|
||||||
|
ConsoleColor.DarkMagenta => "purple",
|
||||||
|
ConsoleColor.DarkYellow => "olive",
|
||||||
|
ConsoleColor.Gray => "silver",
|
||||||
|
ConsoleColor.DarkGray => "grey",
|
||||||
|
ConsoleColor.Blue => "blue",
|
||||||
|
ConsoleColor.Green => "lime",
|
||||||
|
ConsoleColor.Cyan => "aqua",
|
||||||
|
ConsoleColor.Red => "red",
|
||||||
|
ConsoleColor.Magenta => "fuchsia",
|
||||||
|
ConsoleColor.Yellow => "yellow",
|
||||||
|
ConsoleColor.White => "white",
|
||||||
|
_ => "white"
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Renders a horizontal gradient bar (blue → yellow → green) for the given progress [0..1].
|
||||||
|
/// </summary>
|
||||||
|
private static string RenderGradientBar(double progress, int width)
|
||||||
|
{
|
||||||
|
progress = Math.Clamp(progress, 0.0, 1.0);
|
||||||
|
if (width <= 0)
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
int filled = (int)Math.Round(progress * width);
|
||||||
|
int empty = width - filled;
|
||||||
|
|
||||||
|
if (filled <= 0)
|
||||||
|
return $"[grey]{new string('─', width)}[/]";
|
||||||
|
|
||||||
|
// Split filled part into three segments: blue / yellow / green
|
||||||
|
// low progress: mostly blue; mid: yellow; high: green
|
||||||
|
int blueCount = (int)Math.Round(filled * 0.33);
|
||||||
|
int yellowCount = (int)Math.Round(filled * 0.34);
|
||||||
|
int greenCount = filled - blueCount - yellowCount;
|
||||||
|
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
|
if (blueCount > 0)
|
||||||
|
{
|
||||||
|
sb.Append("[blue]");
|
||||||
|
sb.Append(new string('█', blueCount));
|
||||||
|
sb.Append("[/]");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (yellowCount > 0)
|
||||||
|
{
|
||||||
|
sb.Append("[yellow]");
|
||||||
|
sb.Append(new string('█', yellowCount));
|
||||||
|
sb.Append("[/]");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (greenCount > 0)
|
||||||
|
{
|
||||||
|
sb.Append("[green]");
|
||||||
|
sb.Append(new string('█', greenCount));
|
||||||
|
sb.Append("[/]");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty > 0)
|
||||||
|
{
|
||||||
|
sb.Append("[grey]");
|
||||||
|
sb.Append(new string('─', empty));
|
||||||
|
sb.Append("[/]");
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Types & disposal ----
|
||||||
|
|
||||||
|
private readonly record struct ProgressEntry(
|
||||||
|
string Name,
|
||||||
|
double Progress,
|
||||||
|
TimeSpan Eta,
|
||||||
|
double Speed)
|
||||||
|
{
|
||||||
|
public static ProgressEntry Empty => new(string.Empty, 0.0, TimeSpan.Zero, 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly record struct LogEntry(
|
||||||
|
DateTime Timestamp,
|
||||||
|
string Prefix,
|
||||||
|
ConsoleColor Color,
|
||||||
|
string Message);
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_cts.Cancel();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_uiTask?.Wait(500);
|
||||||
|
_inputTask?.Wait(500);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
_cts.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class DynamicGradientBar : IRenderable
|
||||||
|
{
|
||||||
|
private readonly double _progress;
|
||||||
|
|
||||||
|
public DynamicGradientBar(double progress)
|
||||||
|
{
|
||||||
|
_progress = Math.Clamp(progress, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Measurement Measure(RenderOptions options, int maxWidth)
|
||||||
|
{
|
||||||
|
// Use the full width Spectre gives us
|
||||||
|
var width = Math.Max(1, maxWidth);
|
||||||
|
return new Measurement(width, width);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
|
||||||
|
{
|
||||||
|
var width = Math.Max(1, maxWidth);
|
||||||
|
|
||||||
|
// Your gradient bar string WITH markup
|
||||||
|
var bar = RenderGradientBar(_progress, width);
|
||||||
|
|
||||||
|
// Wrap it in a Markup renderable
|
||||||
|
var markup = new Markup(bar);
|
||||||
|
|
||||||
|
// Correct: delegate rendering to Markup
|
||||||
|
foreach (var segment in ((IRenderable)markup).Render(options, maxWidth))
|
||||||
|
yield return segment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
18
TextLogger.cs
Normal file
18
TextLogger.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
namespace splitter;
|
||||||
|
|
||||||
|
public class TextLogger() : ILogger
|
||||||
|
{
|
||||||
|
readonly object _consoleLock = new();
|
||||||
|
|
||||||
|
public void Log(string prefix, ConsoleColor color, string msg)
|
||||||
|
{
|
||||||
|
lock (_consoleLock)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"{prefix} {msg}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DrawProgress(string name, int progressLine, double progress, TimeSpan eta, double speed) {}
|
||||||
|
public void ClearProgress(int progressLevel){}
|
||||||
|
|
||||||
|
}
|
||||||
@ -56,7 +56,7 @@ public class TrackingSplitter : LoggingBase, ISegmentProcessor, IDisposable
|
|||||||
var originalCropWidth = _cropWidth;
|
var originalCropWidth = _cropWidth;
|
||||||
var originalCropHeight = _cropHeight;
|
var originalCropHeight = _cropHeight;
|
||||||
|
|
||||||
Console.WriteLine($"[TrackingSplitter] skip={skip}, duration={duration}, fps={fps}, totalFrames={totalFrames}");
|
LogInfo($"[TrackingSplitter] skip={skip}, duration={duration}, fps={fps}, totalFrames={totalFrames}");
|
||||||
|
|
||||||
var encWidth = _debugOverlay ? videoWidth : originalCropWidth;
|
var encWidth = _debugOverlay ? videoWidth : originalCropWidth;
|
||||||
var encHeight = _debugOverlay ? videoHeight : originalCropHeight;
|
var encHeight = _debugOverlay ? videoHeight : originalCropHeight;
|
||||||
|
|||||||
51
splitter.cs
51
splitter.cs
@ -1,20 +1,58 @@
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using Spectre.Console;
|
||||||
using splitter;
|
using splitter;
|
||||||
|
|
||||||
static class Program
|
static class Program
|
||||||
{
|
{
|
||||||
private static ILogger _logger = null!;
|
private static ILogger _logger = null!;
|
||||||
static async Task Main(string[] args)
|
static async Task<int> Main(string[] args)
|
||||||
{
|
{
|
||||||
var cmd = new CommandLine(args);
|
Task? uiTask = null;
|
||||||
_logger = new Logger(cmd);
|
|
||||||
|
|
||||||
|
var cmd = new CommandLine(args);
|
||||||
|
if ( !cmd.IsValid)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (cmd.PlainText)
|
||||||
|
{
|
||||||
|
_logger = new TextLogger();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.SetBufferSize(Console.WindowWidth, Console.BufferHeight);
|
||||||
|
|
||||||
|
var logger = new SpectreConsoleLogger
|
||||||
|
{
|
||||||
|
Title = "Splitter",
|
||||||
|
NumberOfProcesses = cmd.SingleThreaded ? 1 : Math.Max(1, Environment.ProcessorCount / 2)
|
||||||
|
};
|
||||||
|
_logger = logger;
|
||||||
|
|
||||||
|
using var cts = new CancellationTokenSource();
|
||||||
|
|
||||||
|
uiTask = logger.RunAsync(cts.Token);
|
||||||
|
}
|
||||||
|
|
||||||
|
var success = await ProcessAll(cmd);
|
||||||
|
|
||||||
|
if (uiTask != null)
|
||||||
|
{
|
||||||
|
await uiTask;
|
||||||
|
}
|
||||||
|
if (_logger is IDisposable disposable)
|
||||||
|
disposable.Dispose();
|
||||||
|
|
||||||
|
return success ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<bool> ProcessAll(CommandLine cmd)
|
||||||
|
{
|
||||||
if (!File.Exists(cmd.InputFile))
|
if (!File.Exists(cmd.InputFile))
|
||||||
{
|
{
|
||||||
LogError("Input file not found.");
|
LogError("Input file not found.");
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Directory.Exists(cmd.OutputFolder))
|
if (!Directory.Exists(cmd.OutputFolder))
|
||||||
@ -28,7 +66,7 @@ static class Program
|
|||||||
if (duration <= 0)
|
if (duration <= 0)
|
||||||
{
|
{
|
||||||
LogError("Could not read duration.");
|
LogError("Could not read duration.");
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var target = cmd.OverrideTargetDuration ?? 58.0;
|
var target = cmd.OverrideTargetDuration ?? 58.0;
|
||||||
@ -58,7 +96,7 @@ static class Program
|
|||||||
LogInfo(cmd.ForceFixed
|
LogInfo(cmd.ForceFixed
|
||||||
? $"Fixed segment length: {segmentLength:F2}s (last may be shorter)"
|
? $"Fixed segment length: {segmentLength:F2}s (last may be shorter)"
|
||||||
: $"Equalized segment length: {segmentLength:F2}s");
|
: $"Equalized segment length: {segmentLength:F2}s");
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
LogInfo($"Duration: {duration:F2}s");
|
LogInfo($"Duration: {duration:F2}s");
|
||||||
@ -95,6 +133,7 @@ static class Program
|
|||||||
}
|
}
|
||||||
|
|
||||||
LogInfo("Done.");
|
LogInfo("Done.");
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void LogInfo(string message)
|
private static void LogInfo(string message)
|
||||||
|
|||||||
@ -45,6 +45,7 @@
|
|||||||
<PackageReference Include="Microsoft.ML.OnnxRuntime.DirectML" Version="1.24.4" />
|
<PackageReference Include="Microsoft.ML.OnnxRuntime.DirectML" Version="1.24.4" />
|
||||||
<PackageReference Include="OpenCvSharp4" Version="4.13.0.20260427" />
|
<PackageReference Include="OpenCvSharp4" Version="4.13.0.20260427" />
|
||||||
<PackageReference Include="OpenCvSharp4.runtime.win" Version="4.13.0.20260302" />
|
<PackageReference Include="OpenCvSharp4.runtime.win" Version="4.13.0.20260302" />
|
||||||
|
<PackageReference Include="Spectre.Console" Version="0.55.2" />
|
||||||
<PackageReference Include="UltraFaceDotNet" Version="1.0.0.2" />
|
<PackageReference Include="UltraFaceDotNet" Version="1.0.0.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user