mirror of
https://github.com/unclshura/splitter.git
synced 2026-06-21 16:12:01 +00:00
177 lines
5.1 KiB
C#
177 lines
5.1 KiB
C#
using System.Diagnostics;
|
|
|
|
namespace splitter.probe;
|
|
|
|
public sealed class VideoRotationSampler
|
|
{
|
|
private readonly FrameRotationDetector _detector = new FrameRotationDetector();
|
|
|
|
public static int RotationDetectorSampleCount = 10;
|
|
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(IDictionary<string, string>? overrides)
|
|
{
|
|
if (overrides != null)
|
|
{
|
|
if (overrides.TryGetValue("RotationDetectorSampleCount", out var s))
|
|
RotationDetectorSampleCount = int.Parse(s);
|
|
if (overrides.TryGetValue("RotationDetectorSampleLength", out s))
|
|
RotationDetectorSampleLength = double.Parse(s);
|
|
if (overrides.TryGetValue("RotationDetectorFrameWidth", out s))
|
|
RotationDetectorFrameWidth = int.Parse(s);
|
|
if (overrides.TryGetValue("RotationDetectorFrameHeight", out s))
|
|
RotationDetectorFrameHeight = int.Parse(s);
|
|
}
|
|
|
|
var w = RotationDetectorFrameWidth;
|
|
var 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,
|
|
CancellationToken token)
|
|
{
|
|
if (videoLengthSeconds <= 0)
|
|
return 0;
|
|
|
|
var rotations = new List<int>();
|
|
|
|
for (var i = 0; i < RotationDetectorSampleCount; i++)
|
|
{
|
|
var t = videoLengthSeconds * (i + 1) / (RotationDetectorSampleCount + 1);
|
|
|
|
var frame = await DecodeSingleFrameAsync(
|
|
inputFile,
|
|
t,
|
|
RotationDetectorSampleLength,
|
|
RotationDetectorFrameWidth,
|
|
RotationDetectorFrameHeight,
|
|
token);
|
|
|
|
if (frame != null && !frame.Empty())
|
|
{
|
|
var 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]++;
|
|
}
|
|
|
|
var best = 0;
|
|
var 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,
|
|
CancellationToken token)
|
|
{
|
|
var p = StartFfmpegDecode(inputFile, start, length, rotate: null, plainText: false);
|
|
|
|
var needed = _buffer.Length;
|
|
var read = 0;
|
|
|
|
using var stdout = p.StandardOutput.BaseStream;
|
|
|
|
while (read < needed)
|
|
{
|
|
token.ThrowIfCancellationRequested();
|
|
|
|
var r = await stdout.ReadAsync(_buffer, read, needed - read, token);
|
|
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;
|
|
}
|
|
}
|