Logger is a non static class now. Interface extracted

This commit is contained in:
Alexander Shabarshov 2026-05-12 07:59:26 +01:00
parent 78f1bb17d6
commit 14bcd31efe
8 changed files with 66 additions and 71 deletions

13
ILogger.cs Normal file
View File

@ -0,0 +1,13 @@
namespace splitter;
public interface ILogger
{
void ClearProgress(int progressLevel);
void DrawProgress(string name, int progressLine, double progress, TimeSpan eta, double speed);
void Log(string prefix, ConsoleColor color, string msg);
void LogInfo(string msg) => Log("[INFO]", ConsoleColor.Cyan, msg);
void LogSuccess(string msg) => Log("[ OK ]", ConsoleColor.Green, msg);
void LogWarn(string msg) => Log("[WARN]", ConsoleColor.Yellow, msg);
void LogError(string msg) => Log("[ERR ]", ConsoleColor.Red, msg);
}

View File

@ -1,17 +1,15 @@
namespace splitter; namespace splitter;
public static class Logger public class Logger(CommandLine cmd) : ILogger
{ {
static int _logLines = 0; int _logLines = Math.Max(1, Environment.ProcessorCount / 2) * 2;
static readonly object _consoleLock = new(); readonly object _consoleLock = new();
public static bool PlainText { get; set; } public void Log(string prefix, ConsoleColor color, string msg)
public static void Log(string prefix, ConsoleColor color, string msg)
{ {
lock (_consoleLock) lock (_consoleLock)
{ {
if (PlainText) if (cmd.PlainText)
{ {
Console.WriteLine($"{prefix} {msg}"); Console.WriteLine($"{prefix} {msg}");
} }
@ -21,27 +19,22 @@ public static class Logger
Console.ForegroundColor = ConsoleColor.Cyan; Console.ForegroundColor = ConsoleColor.Cyan;
Console.Write($"{prefix} "); Console.Write($"{prefix} ");
Console.ForegroundColor = color; Console.ForegroundColor = color;
Console.WriteLine(msg); Console.WriteLine(msg);
Console.ResetColor(); Console.ResetColor();
_logLines++; _logLines++;
} }
} }
} }
public static void LogInfo(string msg) => Log("[INFO]", ConsoleColor.Cyan, msg); private readonly Dictionary<int, int> _progressTrack = new();
public static void LogSuccess(string msg) => Log("[ OK ]", ConsoleColor.Green, msg);
public static void LogWarn(string msg) => Log("[WARN]", ConsoleColor.Yellow, msg);
public static void LogError(string msg) => Log("[ERR ]", ConsoleColor.Red, msg);
private static readonly Dictionary<int, int> _progressTrack = new(); public void DrawProgress(string name, int progressLine, double progress, TimeSpan eta, double speed)
public static void DrawProgress(string name, int progressLevel, double progress, TimeSpan eta, double speed)
{ {
if (PlainText || progressLevel < 0) if (cmd.PlainText || progressLine < 0)
return; return;
// Crop name to max 20 chars // Crop name to max 20 chars
@ -60,17 +53,17 @@ public static class Logger
if (filled > barWidth) filled = barWidth; if (filled > barWidth) filled = barWidth;
// --- NEW: skip drawing if visually unchanged --- // --- NEW: skip drawing if visually unchanged ---
if (_progressTrack.TryGetValue(progressLevel, out var lastFilled) && if (_progressTrack.TryGetValue(progressLine, out var lastFilled) &&
lastFilled == filled) lastFilled == filled)
{ {
return; // no visual change → skip return; // no visual change → skip
} }
_progressTrack[progressLevel] = filled; _progressTrack[progressLine] = filled;
// ------------------------------------------------ // ------------------------------------------------
var barLine = _logLines + 1 + progressLevel * 2; var barLine = _logLines + 1 + progressLine * 2;
var infoLine = _logLines + 2 + progressLevel * 2; var infoLine = _logLines + 2 + progressLine * 2;
// Draw progress bar // Draw progress bar
Console.SetCursorPosition(0, barLine); Console.SetCursorPosition(0, barLine);
@ -101,9 +94,9 @@ public static class Logger
} }
public static void ClearProgress(int progressLevel) public void ClearProgress(int progressLevel)
{ {
if (PlainText || progressLevel < 0) if (cmd.PlainText || progressLevel < 0)
return; return;
lock (_consoleLock) lock (_consoleLock)

View File

@ -1,23 +1,23 @@
using System; using System;
namespace splitter; namespace splitter;
public abstract class LoggingBase(int progressLine) public abstract class LoggingBase(ILogger _logger, int _progressLine)
{ {
protected void Log(string level, ConsoleColor color, string message) protected void Log(string level, ConsoleColor color, string message)
=> Logger.Log(level, color, message); => _logger.Log(level, color, message);
protected void LogInfo(string message) protected void LogInfo(string message)
=> Logger.LogInfo(message); => _logger.LogInfo(message);
protected void LogWarn(string message) protected void LogWarn(string message)
=> Logger.LogWarn(message); => _logger.LogWarn(message);
protected void LogError(string message) protected void LogError(string message)
=> Logger.LogError(message); => _logger.LogError(message);
protected void DrawProgress(string name, double percent, TimeSpan eta, double fps) protected void DrawProgress(string name, double percent, TimeSpan eta, double fps)
=> Logger.DrawProgress(name, progressLine, percent, eta, fps); => _logger.DrawProgress(name, _progressLine, percent, eta, fps);
protected void ClearProgress() protected void ClearProgress()
=> Logger.ClearProgress(progressLine); => _logger.ClearProgress(_progressLine);
} }

View File

@ -7,7 +7,7 @@ using FFmpeg.AutoGen;
namespace splitter; namespace splitter;
public class SimpleSplitter(int segmentNo) : LoggingBase(segmentNo), ISegmentProcessor public class SimpleSplitter(int segmentNo, ILogger logger) : LoggingBase(logger, segmentNo), ISegmentProcessor
{ {
public async Task ProcessSegment(string inputFile, string outputFile, double start, double length, string[] passthrough) public async Task ProcessSegment(string inputFile, string outputFile, double start, double length, string[] passthrough)
{ {

View File

@ -18,8 +18,8 @@ public class TrackingSplitter : LoggingBase, ISegmentProcessor, IDisposable
private readonly IObjectDetector _detector; private readonly IObjectDetector _detector;
private readonly CommandLine _cmd; private readonly CommandLine _cmd;
public TrackingSplitter(int segmentNo, int cropWidth, int cropHeight, bool debugOverlay, bool plainText, IObjectDetector detector, CommandLine cmd) public TrackingSplitter(int segmentNo, int cropWidth, int cropHeight, bool debugOverlay, bool plainText, IObjectDetector detector, CommandLine cmd, ILogger logger)
: base(segmentNo) : base(logger, segmentNo)
{ {
_segmentNo = segmentNo; _segmentNo = segmentNo;
_cropWidth = cropWidth; _cropWidth = cropWidth;

View File

@ -9,7 +9,7 @@ public sealed class UltraFaceDetector: LoggingBase, IDisposable, IObjectDetector
{ {
private readonly UltraFace _ultraFace; private readonly UltraFace _ultraFace;
public UltraFaceDetector() : base(-1) public UltraFaceDetector(ILogger logger) : base(logger, -1)
{ {
var basePath = AppDomain.CurrentDomain.BaseDirectory; var basePath = AppDomain.CurrentDomain.BaseDirectory;
var param = new UltraFaceParameter var param = new UltraFaceParameter

View File

@ -59,7 +59,7 @@ public sealed class YoloOnnxObjectDetector : LoggingBase, IObjectDetector, IDisp
} }
} }
public YoloOnnxObjectDetector() : base(-1) public YoloOnnxObjectDetector(ILogger logger) : base(logger, -1)
{ {
var options = new SessionOptions(); var options = new SessionOptions();
options.AppendExecutionProvider_DML(); options.AppendExecutionProvider_DML();

View File

@ -5,49 +5,38 @@ using splitter;
static class Program static class Program
{ {
private static ILogger _logger = null!;
static async Task Main(string[] args) static async Task Main(string[] args)
{ {
var cmd = new CommandLine(args); var cmd = new CommandLine(args);
_logger = new Logger(cmd);
var estimateOnly = cmd.EstimateOnly;
var forceFixed = cmd.ForceFixed; if (!File.Exists(cmd.InputFile))
var passthrough = cmd.Passthrough;
var inputFile = cmd.InputFile;
var outputFolder = cmd.OutputFolder;
(int width, int height)? crop = cmd.Crop;
string? mask = cmd.Mask;
var debug = cmd.Debug;
string? detect = cmd.Detect;
double? overrideTargetDuration = cmd.OverrideTargetDuration;
Logger.PlainText = cmd.PlainText;
if (!File.Exists(inputFile))
{ {
LogError("Input file not found."); LogError("Input file not found.");
return; return;
} }
if (!Directory.Exists(outputFolder)) if (!Directory.Exists(cmd.OutputFolder))
Directory.CreateDirectory(outputFolder); Directory.CreateDirectory(cmd.OutputFolder);
var baseName = Path.GetFileNameWithoutExtension(inputFile);
var outputMask = mask ?? $"{baseName}_Seg%03d.mp4";
var baseName = Path.GetFileNameWithoutExtension(cmd.InputFile);
var outputMask = cmd.Mask ?? $"{baseName}_Seg%03d.mp4";
LogInfo("Reading duration via ffprobe..."); LogInfo("Reading duration via ffprobe...");
var duration = GetDuration(inputFile); var duration = GetDuration(cmd.InputFile);
if (duration <= 0) if (duration <= 0)
{ {
LogError("Could not read duration."); LogError("Could not read duration.");
return; return;
} }
var target = overrideTargetDuration ?? 58.0; var target = cmd.OverrideTargetDuration ?? 58.0;
int segments; int segments;
double segmentLength; double segmentLength;
if (forceFixed) if (cmd.ForceFixed)
{ {
// Fixed chunk size, last one may be shorter // Fixed chunk size, last one may be shorter
segments = (int)Math.Ceiling(duration / target); segments = (int)Math.Ceiling(duration / target);
@ -60,13 +49,13 @@ static class Program
segmentLength = duration / segments; segmentLength = duration / segments;
} }
if (estimateOnly) if (cmd.EstimateOnly)
{ {
LogInfo("=== ESTIMATE MODE ==="); LogInfo("=== ESTIMATE MODE ===");
LogInfo($"Total duration: {duration:F2}s"); LogInfo($"Total duration: {duration:F2}s");
LogInfo($"Target duration: {target:F2}s"); LogInfo($"Target duration: {target:F2}s");
LogInfo($"Segments: {segments}"); LogInfo($"Segments: {segments}");
LogInfo(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;
@ -77,45 +66,45 @@ static class Program
LogInfo($"Equal segment length: {segmentLength:F3}s"); LogInfo($"Equal segment length: {segmentLength:F3}s");
Func<int, ISegmentProcessor> processorFactory; Func<int, ISegmentProcessor> processorFactory;
if (crop != null) if (cmd.Crop != null)
{ {
processorFactory = i => processorFactory = i =>
{ {
IObjectDetector detector = detect switch IObjectDetector detector = cmd.Detect switch
{ {
"face" => new UltraFaceDetector(), "face" => new UltraFaceDetector(_logger),
"body" => new YoloOnnxObjectDetector(), "body" => new YoloOnnxObjectDetector(_logger),
_ => throw new InvalidOperationException($"Unknown detector: {detect}") _ => throw new InvalidOperationException($"Unknown detector: {cmd.Detect}")
}; };
return new TrackingSplitter(i, crop.Value.width, crop.Value.height, debug, cmd.PlainText, detector, cmd); return new TrackingSplitter(i, cmd.Crop.Value.width, cmd.Crop.Value.height, cmd.Debug, cmd.PlainText, detector, cmd, _logger);
}; };
} }
else else
{ {
processorFactory = i => new SimpleSplitter(i); processorFactory = i => new SimpleSplitter(i, _logger);
} }
if (cmd.SingleThreaded) if (cmd.SingleThreaded)
{ {
LogInfo("Starting single-threaded splitting..."); LogInfo("Starting single-threaded splitting...");
await RunSingleThreaded(processorFactory, inputFile, outputFolder, outputMask, duration, segments, segmentLength, passthrough); await RunSingleThreaded(processorFactory, cmd.InputFile, cmd.OutputFolder, outputMask, duration, segments, segmentLength, cmd.Passthrough);
} }
else else
{ {
LogInfo("Starting multi-threaded splitting..."); LogInfo("Starting multi-threaded splitting...");
await RunMultiThreaded(processorFactory, inputFile, outputFolder, outputMask, duration, segments, segmentLength, passthrough); await RunMultiThreaded(processorFactory, cmd.InputFile, cmd.OutputFolder, outputMask, duration, segments, segmentLength, cmd.Passthrough);
} }
LogInfo("Done."); LogInfo("Done.");
} }
private static void LogInfo(string message) private static void LogInfo(string message)
=> Logger.LogInfo(message); => _logger.LogInfo(message);
private static void LogWarn(string message) private static void LogWarn(string message)
=> Logger.LogWarn(message); => _logger.LogWarn(message);
private static void LogError(string message) private static void LogError(string message)
=> Logger.LogError(message); => _logger.LogError(message);
// ----------------------------- // -----------------------------
// ffprobe // ffprobe