diff --git a/ILogger.cs b/ILogger.cs new file mode 100644 index 0000000..3eaf793 --- /dev/null +++ b/ILogger.cs @@ -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); +} \ No newline at end of file diff --git a/Logger.cs b/Logger.cs index 96d16c6..19450ce 100644 --- a/Logger.cs +++ b/Logger.cs @@ -1,17 +1,15 @@ namespace splitter; -public static class Logger +public class Logger(CommandLine cmd) : ILogger { - static int _logLines = 0; - static readonly object _consoleLock = new(); + int _logLines = Math.Max(1, Environment.ProcessorCount / 2) * 2; + readonly object _consoleLock = new(); - public static bool PlainText { get; set; } - - public static void Log(string prefix, ConsoleColor color, string msg) + public void Log(string prefix, ConsoleColor color, string msg) { lock (_consoleLock) { - if (PlainText) + if (cmd.PlainText) { Console.WriteLine($"{prefix} {msg}"); } @@ -21,27 +19,22 @@ public static class Logger Console.ForegroundColor = ConsoleColor.Cyan; Console.Write($"{prefix} "); - + Console.ForegroundColor = color; Console.WriteLine(msg); Console.ResetColor(); - + _logLines++; } } } - public static void LogInfo(string msg) => Log("[INFO]", ConsoleColor.Cyan, msg); - 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 readonly Dictionary _progressTrack = new(); - private static readonly Dictionary _progressTrack = new(); - - public static void DrawProgress(string name, int progressLevel, double progress, TimeSpan eta, double speed) + public void DrawProgress(string name, int progressLine, double progress, TimeSpan eta, double speed) { - if (PlainText || progressLevel < 0) + if (cmd.PlainText || progressLine < 0) return; // Crop name to max 20 chars @@ -60,17 +53,17 @@ public static class Logger if (filled > barWidth) filled = barWidth; // --- NEW: skip drawing if visually unchanged --- - if (_progressTrack.TryGetValue(progressLevel, out var lastFilled) && + if (_progressTrack.TryGetValue(progressLine, out var lastFilled) && lastFilled == filled) { return; // no visual change → skip } - _progressTrack[progressLevel] = filled; + _progressTrack[progressLine] = filled; // ------------------------------------------------ - var barLine = _logLines + 1 + progressLevel * 2; - var infoLine = _logLines + 2 + progressLevel * 2; + var barLine = _logLines + 1 + progressLine * 2; + var infoLine = _logLines + 2 + progressLine * 2; // Draw progress bar 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; lock (_consoleLock) diff --git a/LoggingBase.cs b/LoggingBase.cs index 24fb114..55a5668 100644 --- a/LoggingBase.cs +++ b/LoggingBase.cs @@ -1,23 +1,23 @@ using System; 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) - => Logger.Log(level, color, message); + => _logger.Log(level, color, message); protected void LogInfo(string message) - => Logger.LogInfo(message); + => _logger.LogInfo(message); protected void LogWarn(string message) - => Logger.LogWarn(message); + => _logger.LogWarn(message); protected void LogError(string message) - => Logger.LogError(message); + => _logger.LogError(message); 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() - => Logger.ClearProgress(progressLine); + => _logger.ClearProgress(_progressLine); } diff --git a/SimpleSplitter.cs b/SimpleSplitter.cs index b8b759c..5cbc7e8 100644 --- a/SimpleSplitter.cs +++ b/SimpleSplitter.cs @@ -7,7 +7,7 @@ using FFmpeg.AutoGen; 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) { diff --git a/TrackingSplitter.cs b/TrackingSplitter.cs index c57edd9..590e35e 100644 --- a/TrackingSplitter.cs +++ b/TrackingSplitter.cs @@ -18,8 +18,8 @@ public class TrackingSplitter : LoggingBase, ISegmentProcessor, IDisposable private readonly IObjectDetector _detector; private readonly CommandLine _cmd; - public TrackingSplitter(int segmentNo, int cropWidth, int cropHeight, bool debugOverlay, bool plainText, IObjectDetector detector, CommandLine cmd) - : base(segmentNo) + public TrackingSplitter(int segmentNo, int cropWidth, int cropHeight, bool debugOverlay, bool plainText, IObjectDetector detector, CommandLine cmd, ILogger logger) + : base(logger, segmentNo) { _segmentNo = segmentNo; _cropWidth = cropWidth; diff --git a/UltraFaceDetector.cs b/UltraFaceDetector.cs index e3a1a8f..2f1a92a 100644 --- a/UltraFaceDetector.cs +++ b/UltraFaceDetector.cs @@ -9,7 +9,7 @@ public sealed class UltraFaceDetector: LoggingBase, IDisposable, IObjectDetector { private readonly UltraFace _ultraFace; - public UltraFaceDetector() : base(-1) + public UltraFaceDetector(ILogger logger) : base(logger, -1) { var basePath = AppDomain.CurrentDomain.BaseDirectory; var param = new UltraFaceParameter diff --git a/YoloOnnxObjectDetector.cs b/YoloOnnxObjectDetector.cs index a8553d4..b487760 100644 --- a/YoloOnnxObjectDetector.cs +++ b/YoloOnnxObjectDetector.cs @@ -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(); options.AppendExecutionProvider_DML(); diff --git a/splitter.cs b/splitter.cs index 881e966..258b80a 100644 --- a/splitter.cs +++ b/splitter.cs @@ -5,49 +5,38 @@ using splitter; static class Program { + private static ILogger _logger = null!; static async Task Main(string[] args) { var cmd = new CommandLine(args); - - var estimateOnly = cmd.EstimateOnly; - var forceFixed = cmd.ForceFixed; - 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)) + _logger = new Logger(cmd); + + if (!File.Exists(cmd.InputFile)) { LogError("Input file not found."); return; } - if (!Directory.Exists(outputFolder)) - Directory.CreateDirectory(outputFolder); - - var baseName = Path.GetFileNameWithoutExtension(inputFile); - var outputMask = mask ?? $"{baseName}_Seg%03d.mp4"; + if (!Directory.Exists(cmd.OutputFolder)) + Directory.CreateDirectory(cmd.OutputFolder); + var baseName = Path.GetFileNameWithoutExtension(cmd.InputFile); + var outputMask = cmd.Mask ?? $"{baseName}_Seg%03d.mp4"; LogInfo("Reading duration via ffprobe..."); - var duration = GetDuration(inputFile); + var duration = GetDuration(cmd.InputFile); if (duration <= 0) { LogError("Could not read duration."); return; } - var target = overrideTargetDuration ?? 58.0; + var target = cmd.OverrideTargetDuration ?? 58.0; int segments; double segmentLength; - if (forceFixed) + if (cmd.ForceFixed) { // Fixed chunk size, last one may be shorter segments = (int)Math.Ceiling(duration / target); @@ -60,13 +49,13 @@ static class Program segmentLength = duration / segments; } - if (estimateOnly) + if (cmd.EstimateOnly) { LogInfo("=== ESTIMATE MODE ==="); LogInfo($"Total duration: {duration:F2}s"); LogInfo($"Target duration: {target:F2}s"); LogInfo($"Segments: {segments}"); - LogInfo(forceFixed + LogInfo(cmd.ForceFixed ? $"Fixed segment length: {segmentLength:F2}s (last may be shorter)" : $"Equalized segment length: {segmentLength:F2}s"); return; @@ -77,45 +66,45 @@ static class Program LogInfo($"Equal segment length: {segmentLength:F3}s"); Func processorFactory; - if (crop != null) + if (cmd.Crop != null) { processorFactory = i => { - IObjectDetector detector = detect switch + IObjectDetector detector = cmd.Detect switch { - "face" => new UltraFaceDetector(), - "body" => new YoloOnnxObjectDetector(), - _ => throw new InvalidOperationException($"Unknown detector: {detect}") + "face" => new UltraFaceDetector(_logger), + "body" => new YoloOnnxObjectDetector(_logger), + _ => 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 { - processorFactory = i => new SimpleSplitter(i); + processorFactory = i => new SimpleSplitter(i, _logger); } if (cmd.SingleThreaded) { 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 { 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."); } private static void LogInfo(string message) - => Logger.LogInfo(message); + => _logger.LogInfo(message); private static void LogWarn(string message) - => Logger.LogWarn(message); + => _logger.LogWarn(message); private static void LogError(string message) - => Logger.LogError(message); + => _logger.LogError(message); // ----------------------------- // ffprobe