Compare commits

..

No commits in common. "93de483bc6c411e6fb446aea87fb00716716cab5" and "1fd938416e7417d19eaeb3a04bab0a818a0370c0" have entirely different histories.

20 changed files with 217 additions and 461 deletions

View File

@ -2,11 +2,9 @@ name: Build and Publish
on:
push:
tags:
- 'v*'
permissions:
contents: write
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
@ -22,34 +20,22 @@ jobs:
dotnet-version: 10.0.x
- name: Restore
run: dotnet restore -r win-x64
run: dotnet restore
- name: 'Get Version'
id: version
uses: battila7/get-version-action@v2
- name: Build Release
env:
GITHUB_RUN_NUMBER: ${{ github.run_number }}
GITHUB_SHA: ${{ github.sha }}
run: dotnet build -c Release --no-restore
- name: Publish Release
run: dotnet publish splitter-cli/splitter.csproj -c Release -r win-x64 /p:Version=${{ steps.version.outputs.version-without-v }} /p:BuildNumber=${{ github.run_number }} /p:SourceRevisionId=${{ github.sha }}
- name: Create ZIP
shell: pwsh
run: |
$publish = "splitter-cli/bin/Release/net10.0/win-x64/publish"
$version = "${{ steps.version.outputs.version-without-v }}"
$zip = "splitter-win-x64-$version.zip"
if (Test-Path $zip) { Remove-Item $zip }
Compress-Archive -Path "$publish/*" -DestinationPath $zip
Write-Host "Created $zip"
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
name: "Release ${{ github.ref_name }}"
files: splitter-win-x64-${{ steps.version.outputs.version-without-v }}.zip
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_RUN_NUMBER: ${{ github.run_number }}
GITHUB_SHA: ${{ github.sha }}
run: dotnet publish -c Release -r win-x64 --self-contained true --no-build
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: splitter-win-x64
path: bin/Release/net10.0/win-x64/publish/

View File

@ -45,6 +45,7 @@ model.export(format="onnx", opset=12, half=False) # FP32 ONNX
6. Applies rotation, crop, and tracking if enabled
7. Displays progress, ETA, and speed
---
## Face Tracking vs Body Tracking
Face tracking and body tracking serve different purposes, and Splitter supports both because each
@ -103,24 +104,7 @@ This prevents the crop from drifting offscreen and ensures that the output re
when tracking fails. All positions are clamped to valid bounds, guaranteeing that the crop window
never leaves the video frame.
### Automatic rotation detection
The rotationestimation method is based on analyzing the distribution of gradient orientations within
a video frame. After converting the frame to grayscale, the algorithm computes horizontal and vertical
image gradients using Sobel operators and derives perpixel gradient magnitudes and orientations.
These orientations are folded into the range [0, 180) and accumulated into a fixedsize,
magnitudeweighted histogram. The histogram represents the structural edge distribution of the frame,
independent of brightness fluctuations or local lighting artifacts. By comparing the total gradient
energy concentrated near 0 degrees (vertical edges) with the energy near 90 degrees (horizontal edges),
the method determines whether the frame is more consistent with an upright or sideways orientation.
This approach is designed for environments where brightnessbased cues are unreliable, such as
live concerts with strobe lights, LED walls, haze, and crowd movement. It relies solely on geometric
edge structure, which remains stable even under extreme lighting variation. The implementation is
optimized for highthroughput video processing: all intermediate Mats, buffers, and histograms are
preallocated, and pixel data is accessed directly through pointers to avoid perframe memory
allocation. The method is intentionally biased toward the upright orientation, returning a sideways
classification only when the horizontaledge energy significantly exceeds the verticaledge energy.
---
## Usage
@ -130,6 +114,7 @@ splitter [<input.mp4> ...] [options] [--] <ffmpeg passthrough>
Inputs may be provided directly, via `--file=...`, or using file masks such as `videos/*.mp4`.
---
## Options
@ -148,7 +133,6 @@ All option names are preserved exactly, and descriptions are consolidated for cl
| **--duration=<value>** | Override target segment duration. Formats: `Ns`, `NmMs`, `N`. Examples: `--duration=90s`, `--duration=2m30s`, `--duration=45`. Without `--force`: max 58 seconds, equalized across segments. |
| **--force** | Use the duration exactly as provided. Last segment may be shorter. |
| **--rotate=<degrees>** | Rotate video by 90, 180, or 270 degrees. Useful for correcting orientation metadata. |
| **--rotate-auto** | Use automatic rotation detection. |
| **--estimate** | Print calculated segment information and exit. No splitting is performed. |
| **--crop[=<w:h>]** | Crop video to a target width and height with face/body tracking. Default: 607x1080. Ideal for Shorts, TikTok, Reels. |
| **--detect=<name>** | Object detector for tracking. Values: `face` (UltraFace), `body` (YoloOnnx, default), `none` (center crop). |
@ -158,6 +142,8 @@ All option names are preserved exactly, and descriptions are consolidated for cl
| **--debug** | Show debug overlay during tracking. No cropping performed, but crop region shown. |
| **-p:<name>=<value>** | Set custom parameters for the object detector. Example: `-p:confidence=0.5`. Defaults: DropoutToleranceFrames=20, EmaFactor=0.65, CameraEasing=0.03, LostFreezeFrames=60. |
---
## FFmpeg Passthrough
Anything after `--` is passed directly to FFmpeg.
@ -167,12 +153,16 @@ Example:
splitter video.mp4 --force --duration=45 -- -an -sn
```
---
## Input and Output Behavior
- `input.mp4` may be a file mask (`videos/*.mp4`)
- Output filenames follow the `--mask` pattern
- Output folder defaults to `<input folder>/Splitter` unless overridden
---
## Examples
Split into equal 60second segments:

View File

@ -1,12 +1,8 @@
using System.Reflection;
namespace splitter;
namespace splitter;
public static class BuildInfo
{
private static readonly Assembly Assembly = typeof(BuildInfo).Assembly;
public static string Version => Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion ?? "unknown";
public static string FileVersion => Assembly.GetCustomAttribute<AssemblyFileVersionAttribute>()?.Version ?? "unknown";
public static string AssemblyVersion => Assembly.GetName().Version?.ToString() ?? "unknown";
public static string Version { get; } = ThisAssembly.Version;
public static string BuildNumber { get; } = ThisAssembly.BuildNumber;
public static string Commit { get; } = ThisAssembly.Commit;
}

View File

@ -0,0 +1,9 @@
// Auto-generated. Do not edit.
namespace splitter;
internal static class ThisAssembly
{
public const string Version = "@VERSION@";
public const string BuildNumber = "@BUILDNUMBER@";
public const string Commit = "@COMMIT@";
}

View File

@ -1,4 +1,5 @@
using OpenCvSharp;
using System;
using OpenCvSharp;
namespace splitter;

View File

@ -1,4 +1,8 @@
using System.Globalization;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using OpenCvSharp;
namespace splitter;
@ -18,7 +22,6 @@ public class SingleJob
public bool ForceFixed { get; set; }
public bool SingleThreaded { get; set; }
public int? Rotate { get; set; }
public bool RotateAuto { get; set; }
public Dictionary<string, string> Parameters { get; set; } = [];
public void Override<T>(ref T member, string name)
@ -140,10 +143,6 @@ public sealed class CommandLine
{
Master.SingleThreaded = true;
}
else if (arg == "--rotate-auto")
{
Master.RotateAuto = true;
}
else if (arg.StartsWith("--gravitate="))
{
var val = arg.Substring("--gravitate=".Length);
@ -198,7 +197,6 @@ public sealed class CommandLine
ForceFixed = Master.ForceFixed,
SingleThreaded = Master.SingleThreaded,
Rotate = Master.Rotate,
RotateAuto = Master.RotateAuto,
Parameters = new Dictionary<string, string>(Master.Parameters)
}).ToArray();
@ -330,7 +328,7 @@ public sealed class CommandLine
public static void PrintVersion()
{
Console.WriteLine($"...---=== splitter version {BuildInfo.Version} (file version: {BuildInfo.FileVersion}, build {BuildInfo.AssemblyVersion}) ===---...");
Console.WriteLine($"...---=== splitter version {BuildInfo.Version} (commit {BuildInfo.Commit}, build {BuildInfo.BuildNumber}) ===---...");
}
public static void PrintHelp()
@ -373,9 +371,6 @@ Options:
--rotate=<degrees> Rotate video by specified degrees (90, 180, 270).
Useful for videos with incorrect orientation metadata.
--rotate-auto Auto-detect rotation and rotate accordingly.
Uses edge orientation statistics to determine if video is rotated.
--estimate Print calculated segment information and exit.
No splitting is performed.
@ -398,7 +393,7 @@ Options:
--debug Show debug overlay during face tracking.
-p:<name>=<value> Set a custom parameter for the object detector.
Example: -p:EmaFactor=0.65
Example: -p:confidence=0.5
Tracking splitter defaults:
DropoutToleranceFrames = 20;
@ -406,12 +401,6 @@ Options:
CameraEasing = 0.03;
LostFreezeFrames = 60;
Rotation detector defaults:
RotationDetectorSampleCount = 5;
RotationDetectorSampleLength = 0.15;
RotationDetectorFrameWidth = 320;
RotationDetectorFrameHeight = 180;
Passthrough:
Anything after -- is passed directly to ffmpeg.

View File

@ -1,102 +0,0 @@
using OpenCvSharp;
namespace splitter;
public sealed class FrameRotationDetector
{
private readonly Mat _gray;
private readonly Mat _gx;
private readonly Mat _gy;
private readonly Mat _mag;
private readonly Mat _angle;
private readonly float[] _hist;
private readonly int _w;
private readonly int _h;
private readonly int _bins;
public FrameRotationDetector(int width = 320, int height = 180, int bins = 36)
{
_w = width;
_h = height;
_bins = bins;
_gray = new Mat(height, width, MatType.CV_8UC1);
_gx = new Mat(height, width, MatType.CV_32F);
_gy = new Mat(height, width, MatType.CV_32F);
_mag = new Mat(height, width, MatType.CV_32F);
_angle = new Mat(height, width, MatType.CV_32F);
_hist = new float[bins]; // allocated once
}
public int GetRotation(Mat frame)
{
// 1. Grayscale
Cv2.CvtColor(frame, _gray, ColorConversionCodes.BGR2GRAY);
// 2. Sobel
Cv2.Sobel(_gray, _gx, MatType.CV_32F, 1, 0, 3);
Cv2.Sobel(_gray, _gy, MatType.CV_32F, 0, 1, 3);
// 3. Magnitude + angle
Cv2.CartToPolar(_gx, _gy, _mag, _angle, angleInDegrees: true);
// 4. Clear histogram
for (int i = 0; i < _bins; i++)
_hist[i] = 0;
float binSize = 180f / _bins;
unsafe
{
float* anglePtr = (float*)_angle.Data;
float* magPtr = (float*)_mag.Data;
int total = _w * _h;
for (int i = 0; i < total; i++)
{
float m = magPtr[i];
if (m < 5f) continue; // ignore weak gradients
float a = anglePtr[i];
if (a < 0) a += 360f;
a = a % 180f;
int bin = (int)(a / binSize);
if (bin < 0) bin = 0;
if (bin >= _bins) bin = _bins - 1;
_hist[bin] += m;
}
}
// 5. Energy around 0° vs 90°
float e0 = 0, e90 = 0;
int window = 3;
int bin0 = 0;
int bin90 = _bins / 2;
for (int i = -window; i <= window; i++)
{
e0 += _hist[Wrap(bin0 + i)];
e90 += _hist[Wrap(bin90 + i)];
}
// 6. Decide upright vs sideways
if (e90 > e0 * 1.6f)
return 90; // sideways
return 0; // upright (concert default)
}
private int Wrap(int b)
{
if (b < 0) return b + _bins;
if (b >= _bins) return b - _bins;
return b;
}
}

View File

@ -4,5 +4,5 @@ namespace splitter;
public interface IObjectDetector : IDisposable
{
List<(Rect box, Point2f center)> DetectAll(Mat frameCont);
List<(Rect box, Point2f center)> DetectAll(Mat frameCont, int width, int height);
}

View File

@ -1,4 +1,8 @@
namespace splitter;
using System;
using System.Collections.Generic;
using System.Text;
namespace splitter;
public interface ISegmentProcessor
{

View File

@ -1,4 +1,5 @@
namespace splitter;
using System;
namespace splitter;
public abstract class LoggingBase(ILogger _logger, int _progressLine)
{

View File

@ -1,5 +1,8 @@
using System.Diagnostics;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Text;
namespace splitter;
@ -8,31 +11,12 @@ public record VideoInfo(
int Width,
int Height,
double Fps,
double Bitrate,
int Rotation = 0
double Bitrate
);
public static class ProbeVideo
{
public static async Task<VideoInfo> Probe(SingleJob job)
{
var info = ProbeSize(job.InputFile);
if ( job.RotateAuto)
{
var rotation = await ProbeRotation(job, info.Duration);
info = info with { Rotation = rotation };
}
return info;
}
private static async Task<int> ProbeRotation(SingleJob job, double duration)
{
var rotation = await new VideoRotationSampler(job).DetectRotationAsync(job.InputFile, duration);
return rotation;
}
private static VideoInfo ProbeSize(string inputFile)
public static VideoInfo Probe(string inputFile)
{
var args =
"-v error " +

View File

@ -10,7 +10,7 @@
},
"Debug": {
"commandName": "Project",
"commandLineArgs": "\"C:\\Users\\uncls\\Pictures\\2026\\2026 - Laura Cox\\Video\\MAH00041.MP4\" --rotate-auto"
"commandLineArgs": "\"C:\\Users\\uncls\\Pictures\\2026\\2026 - Secret Rule\\20260426_212004.mp4\" --crop --detect=body --debug --single-thread --text"
}
}
}

View File

@ -1,5 +1,9 @@
using System.Diagnostics;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Text;
using FFmpeg.AutoGen;
namespace splitter;

View File

@ -1,4 +1,9 @@
using System.Text;
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;
@ -173,7 +178,7 @@ public sealed class SpectreConsoleLogger : ILogger, IDisposable
var layout = new Layout("root")
.SplitRows(
new Layout("progress") { Size = Math.Max(3, numberOfProcessesSnapshot + 4) },
new Layout("progress") { Size = Math.Max(3, numberOfProcessesSnapshot + 2) },
new Layout("log")
//new Layout("buttons") { Size = 3 }
);
@ -228,8 +233,8 @@ public sealed class SpectreConsoleLogger : ILogger, IDisposable
foreach (var log in slice)
{
var time = log.Timestamp.ToString("HH:mm:ss");
var timeColor = "deepskyblue1";
var prefixColor = "lightpink1";
var timeColor = "deepskyblue1"; // dark-ish blue
var prefixColor = "lightpink1"; // light magenta
var msgColor = MapConsoleColor(log.Color);
var line =
@ -240,10 +245,38 @@ public sealed class SpectreConsoleLogger : ILogger, IDisposable
rows.Add(new Markup(line));
}
if (rows.Count == 0)
return new Markup("[grey]No log messages yet.[/]");
IRenderable content =
rows.Count == 0
? new Markup("[grey]No log messages yet.[/]")
: new Rows(rows);
return 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 ----
@ -254,23 +287,23 @@ public sealed class SpectreConsoleLogger : ILogger, IDisposable
private static string MapConsoleColor(ConsoleColor color) =>
color switch
{
ConsoleColor.Black => "black",
ConsoleColor.DarkBlue => "navy",
ConsoleColor.DarkGreen => "green",
ConsoleColor.DarkCyan => "teal",
ConsoleColor.DarkRed => "maroon",
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"
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>

View File

@ -1,6 +1,9 @@
using System.Diagnostics;
using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using OpenCvSharp;
namespace splitter;
@ -109,7 +112,7 @@ public class TrackingSplitter : LoggingBase, ISegmentProcessor, IDisposable
// input frame → Mat
Marshal.Copy(inBuffer, 0, frameMat.Data, inBytes);
var objects = _detector.DetectAll(frameMat);
var objects = _detector.DetectAll(frameMat, videoWidth, videoHeight);
var primary = SelectTrackedObject(objects, kalman.LastMeasurement);
camera.Update(primary);

View File

@ -1,4 +1,5 @@
using System.Runtime.InteropServices;
using NcnnDotNet.Layers;
using OpenCvSharp;
using UltraFaceDotNet;
@ -24,7 +25,7 @@ public sealed class UltraFaceDetector: LoggingBase, IDisposable, IObjectDetector
_ultraFace = UltraFace.Create(param);
}
public List<(Rect box, Point2f center)> DetectAll(Mat frameCont)
public List<(Rect box, Point2f center)> DetectAll(Mat frameCont, int width, int height)
{
// Convert to byte[] for UltraFace
var bytesFull = frameCont.Rows * frameCont.Cols * frameCont.ElemSize();
@ -43,8 +44,8 @@ public sealed class UltraFaceDetector: LoggingBase, IDisposable, IObjectDetector
using var mat = NcnnDotNet.Mat.FromPixels(
(IntPtr)p,
NcnnDotNet.PixelType.Bgr, // BGR24 input
frameCont.Width,
frameCont.Height);
width,
height);
var faces = _ultraFace.Detect(mat);
if (faces == null)

View File

@ -1,169 +0,0 @@
using OpenCvSharp;
using System.Diagnostics;
namespace splitter;
public sealed class VideoRotationSampler
{
private readonly FrameRotationDetector _detector = new FrameRotationDetector();
public static int RotationDetectorSampleCount = 20;
public static double RotationDetectorSampleLength = 0.15; // seconds to decode per probe
public static int RotationDetectorFrameWidth = 320;
public static int RotationDetectorFrameHeight = 180;
// --- Zero-allocation buffers ---
private readonly byte[] _buffer;
private readonly Mat _frameMat;
public VideoRotationSampler(SingleJob _master)
{
if (_master.Parameters.TryGetValue("RotationDetectorSampleCount", out var s))
RotationDetectorSampleCount = int.Parse(s);
if (_master.Parameters.TryGetValue("RotationDetectorSampleLength", out s))
RotationDetectorSampleLength = double.Parse(s);
if (_master.Parameters.TryGetValue("RotationDetectorFrameWidth", out s))
RotationDetectorFrameWidth = int.Parse(s);
if (_master.Parameters.TryGetValue("RotationDetectorFrameHeight", out s))
RotationDetectorFrameHeight = int.Parse(s);
int w = RotationDetectorFrameWidth;
int h = RotationDetectorFrameHeight;
_buffer = new byte[w * h * 3]; // raw BGR24 buffer
_frameMat = new Mat(h, w, MatType.CV_8UC3); // wraps buffer
}
public async Task<int> DetectRotationAsync(
string inputFile,
double videoLengthSeconds)
{
if (videoLengthSeconds <= 0)
return 0;
var rotations = new List<int>();
for (int i = 0; i < RotationDetectorSampleCount; i++)
{
double t = videoLengthSeconds * (i + 1) / (RotationDetectorSampleCount + 1);
var frame = await DecodeSingleFrameAsync(
inputFile,
t,
RotationDetectorSampleLength,
RotationDetectorFrameWidth,
RotationDetectorFrameHeight);
if (frame != null && !frame.Empty())
{
int rot = _detector.GetRotation(frame);
rotations.Add(rot);
}
}
if (rotations.Count == 0)
return 0;
return Majority(rotations);
}
private static int Majority(List<int> values)
{
var counts = new Dictionary<int, int>();
foreach (var v in values)
{
if (!counts.ContainsKey(v)) counts[v] = 0;
counts[v]++;
}
int best = 0;
int bestCount = 0;
foreach (var kv in counts)
{
if (kv.Value > bestCount)
{
best = kv.Key;
bestCount = kv.Value;
}
}
return best;
}
private async Task<Mat?> DecodeSingleFrameAsync(
string inputFile,
double start,
double length,
int width,
int height)
{
var p = StartFfmpegDecode(inputFile, start, length, rotate: null, plainText: false);
int needed = _buffer.Length;
int read = 0;
using var stdout = p.StandardOutput.BaseStream;
while (read < needed)
{
int r = await stdout.ReadAsync(_buffer, read, needed - read);
if (r == 0)
return null;
read += r;
}
try { p.Kill(); } catch { }
// Copy buffer → Mat (no new Mat)
System.Runtime.InteropServices.Marshal.Copy(_buffer, 0, _frameMat.Data, _buffer.Length);
return _frameMat;
}
private Process StartFfmpegDecode(
string inputFile,
double start,
double length,
int? rotate,
bool plainText)
{
var ss = start.ToString("0.###", System.Globalization.CultureInfo.InvariantCulture);
var t = length.ToString("0.###", System.Globalization.CultureInfo.InvariantCulture);
// FFmpeg does the resize + format conversion
var args =
$"-ss {ss} -t {t} -i \"{inputFile}\" " +
"-an -sn " +
$"-vf scale={RotationDetectorFrameWidth}:{RotationDetectorFrameHeight},format=bgr24 " +
"-f rawvideo -";
var psi = new ProcessStartInfo
{
FileName = "ffmpeg",
Arguments = args,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};
var p = new Process { StartInfo = psi };
p.Start();
// Optional stderr logging
_ = Task.Run(() =>
{
try
{
string? line;
while ((line = p.StandardError.ReadLine()) != null)
if (plainText)
Console.WriteLine($"[ffmpeg-decode] {line}");
}
catch { }
});
return p;
}
}

View File

@ -1,4 +1,8 @@
using System.Runtime.CompilerServices;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using Microsoft.ML.OnnxRuntime;
using Microsoft.ML.OnnxRuntime.Tensors;
using OpenCvSharp;
@ -79,7 +83,7 @@ public sealed class YoloOnnxObjectDetector : LoggingBase, IObjectDetector, IDisp
_inputs.Add(NamedOnnxValue.CreateFromTensor(_inputName, _inputTensor));
}
public List<(Rect box, Point2f center)> DetectAll(Mat frameCont)
public List<(Rect box, Point2f center)> DetectAll(Mat frameCont, int width, int height)
{
if (frameCont.Empty())
{

View File

@ -1,5 +1,7 @@
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Globalization;
using System.Text;
using Spectre.Console;
using splitter;
@ -76,7 +78,7 @@ static partial class Program
if (!Directory.Exists(job.OutputFolder))
Directory.CreateDirectory(job.OutputFolder);
var info = await ProbeVideo.Probe(job);
var info = ProbeVideo.Probe(job.InputFile);
if (info.Duration <= 0)
{
LogError($"{baseName}: Could not read duration.");

View File

@ -27,18 +27,40 @@
<EnableAVX2>true</EnableAVX2>
<DebugType>none</DebugType>
<DebugSymbols>false</DebugSymbols>
<SelfContained>true</SelfContained>
<PublishSingleFile>true</PublishSingleFile>
<PublishTrimmed>false</PublishTrimmed>
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
<PublishReadyToRun>true</PublishReadyToRun>
<PublishReadyToRun>false</PublishReadyToRun>
</PropertyGroup>
<PropertyGroup>
<Version>1.0.0</Version>
<InformationalVersion>$(Version).$(BuildNumber)+$(SourceRevisionId)</InformationalVersion>
<AssemblyVersion>$(Version)</AssemblyVersion>
<FileVersion>$(Version).$(BuildNumber)</FileVersion>
<SourceRevisionId>$(GITHUB_SHA)</SourceRevisionId>
</PropertyGroup>
<Target Name="RemoveUnwantedFiles" AfterTargets="Publish" Condition="'$(Configuration)' == 'Release'">
<PropertyGroup>
<BuildNumber>$(GITHUB_RUN_NUMBER)</BuildNumber>
</PropertyGroup>
<Target Name="GenerateBuildInfo" BeforeTargets="BeforeCompile">
<ReadLinesFromFile File="BuildInfo.template">
<Output TaskParameter="Lines" ItemName="BuildInfoLines" />
</ReadLinesFromFile>
<ItemGroup>
<ProcessedBuildInfoLines Include="@(BuildInfoLines->Replace('@VERSION@', '$(Version)')->Replace('@BUILDNUMBER@', '$(BuildNumber)')->Replace('@COMMIT@', '$(SourceRevisionId)'))" />
</ItemGroup>
<WriteLinesToFile
File="BuildInfo.g.cs"
Overwrite="true"
Lines="@(ProcessedBuildInfoLines)" />
</Target>
<Target Name="RemoveUnwantedFiles" AfterTargets="Publish" Condition="'$(Configuration)' == 'Release'">
<ItemGroup>
<FilesToDelete Include="$(PublishDir)**\*.pdb" />
<FilesToDelete Include="$(PublishDir)**\*.lib" />
@ -49,8 +71,6 @@
<ItemGroup>
<Compile Update="ThisAssembly.g.cs" />
<Content Include="models/*.*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>