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 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 encHeight = _debugOverlay ? videoHeight : originalCropHeight;
|
||||
|
||||
51
splitter.cs
51
splitter.cs
@ -1,20 +1,58 @@
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using Spectre.Console;
|
||||
using splitter;
|
||||
|
||||
static class Program
|
||||
{
|
||||
private static ILogger _logger = null!;
|
||||
static async Task Main(string[] args)
|
||||
static async Task<int> Main(string[] args)
|
||||
{
|
||||
var cmd = new CommandLine(args);
|
||||
_logger = new Logger(cmd);
|
||||
Task? uiTask = null;
|
||||
|
||||
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))
|
||||
{
|
||||
LogError("Input file not found.");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Directory.Exists(cmd.OutputFolder))
|
||||
@ -28,7 +66,7 @@ static class Program
|
||||
if (duration <= 0)
|
||||
{
|
||||
LogError("Could not read duration.");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
var target = cmd.OverrideTargetDuration ?? 58.0;
|
||||
@ -58,7 +96,7 @@ static class Program
|
||||
LogInfo(cmd.ForceFixed
|
||||
? $"Fixed segment length: {segmentLength:F2}s (last may be shorter)"
|
||||
: $"Equalized segment length: {segmentLength:F2}s");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
LogInfo($"Duration: {duration:F2}s");
|
||||
@ -95,6 +133,7 @@ static class Program
|
||||
}
|
||||
|
||||
LogInfo("Done.");
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void LogInfo(string message)
|
||||
|
||||
@ -45,6 +45,7 @@
|
||||
<PackageReference Include="Microsoft.ML.OnnxRuntime.DirectML" Version="1.24.4" />
|
||||
<PackageReference Include="OpenCvSharp4" Version="4.13.0.20260427" />
|
||||
<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" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user