mirror of
https://github.com/unclshura/splitter.git
synced 2026-08-06 03:33:39 +00:00
Separated pass-through and rotating splitters. Rotation fixed.
This commit is contained in:
parent
3457aa6816
commit
e986964f00
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
public sealed class BufferPool : IBufferPool
|
public sealed class BufferPool : IBufferPool
|
||||||
{
|
{
|
||||||
private readonly int _capacity;
|
|
||||||
|
|
||||||
public sealed class Entry
|
public sealed class Entry
|
||||||
{
|
{
|
||||||
@ -18,10 +17,14 @@ public sealed class BufferPool : IBufferPool
|
|||||||
Bgr = new byte[w * h * 3];
|
Bgr = new byte[w * h * 3];
|
||||||
Bgra = new byte[w * h * 4];
|
Bgra = new byte[w * h * 4];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override public string ToString() => $"Entry({Width}x{Height})";
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly Dictionary<(int w, int h), LinkedListNode<Entry>> _map;
|
private readonly Dictionary<(int w, int h), LinkedListNode<Entry>> _map;
|
||||||
private readonly LinkedList<Entry> _lru;
|
private readonly LinkedList<Entry> _lru;
|
||||||
|
private readonly int _capacity;
|
||||||
|
private readonly Lock _lock = new();
|
||||||
|
|
||||||
public BufferPool()
|
public BufferPool()
|
||||||
{
|
{
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
x:DataType="vm:PreviewPaneViewModel">
|
x:DataType="vm:PreviewPaneViewModel">
|
||||||
|
|
||||||
<Border Background="#202020" Padding="10">
|
<Border Background="#202020" Padding="10">
|
||||||
<Grid RowDefinitions="*,Auto,Auto">
|
<Grid RowDefinitions="*,Auto">
|
||||||
|
|
||||||
<local:PreviewCanvas
|
<local:PreviewCanvas
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
@ -19,7 +19,7 @@
|
|||||||
DetectAbove="{Binding DetectAbove, Mode=TwoWay}"
|
DetectAbove="{Binding DetectAbove, Mode=TwoWay}"
|
||||||
DetectId="{Binding Selected.DetectId, Mode=TwoWay}"
|
DetectId="{Binding Selected.DetectId, Mode=TwoWay}"
|
||||||
/>
|
/>
|
||||||
|
<!--
|
||||||
<Grid Grid.Row="1"
|
<Grid Grid.Row="1"
|
||||||
ColumnDefinitions="Auto"
|
ColumnDefinitions="Auto"
|
||||||
Margin="0,10,0,0">
|
Margin="0,10,0,0">
|
||||||
@ -37,8 +37,8 @@
|
|||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
|
-->
|
||||||
<Grid Grid.Row="2"
|
<Grid Grid.Row="1"
|
||||||
ColumnDefinitions="Auto,*,Auto"
|
ColumnDefinitions="Auto,*,Auto"
|
||||||
Margin="0,10,0,0">
|
Margin="0,10,0,0">
|
||||||
|
|
||||||
|
|||||||
@ -57,7 +57,11 @@ public class JobProcessor(ILogger logger) : LoggingBase(logger, 0), IJobProcesso
|
|||||||
return [];
|
return [];
|
||||||
|
|
||||||
Func<int, ISegmentProcessor> processorFactory;
|
Func<int, ISegmentProcessor> processorFactory;
|
||||||
if (job.Crop != null)
|
if (job.Rotate != null && job.Rotate != 0)
|
||||||
|
{
|
||||||
|
processorFactory = i => new RotatingSplitter(i, _logger);
|
||||||
|
}
|
||||||
|
else if (job.Crop != null && job.Detect != null && job.Detect != "none")
|
||||||
{
|
{
|
||||||
processorFactory = i =>
|
processorFactory = i =>
|
||||||
{
|
{
|
||||||
@ -75,7 +79,7 @@ public class JobProcessor(ILogger logger) : LoggingBase(logger, 0), IJobProcesso
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
processorFactory = i => new SimpleSplitter(i, _logger);
|
processorFactory = i => new PassthroughSplitter(i, _logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
var segmentsToUse = predefinedSegments;
|
var segmentsToUse = predefinedSegments;
|
||||||
|
|||||||
103
splitter-cli/PassthroughSplitter.cs
Normal file
103
splitter-cli/PassthroughSplitter.cs
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
namespace splitter;
|
||||||
|
|
||||||
|
public sealed class PassthroughSplitter : LoggingBase, ISegmentProcessor
|
||||||
|
{
|
||||||
|
private sealed class State : IFrameProcessingState
|
||||||
|
{
|
||||||
|
public Process? EncodeProcess { get; set; }
|
||||||
|
|
||||||
|
public string InputFile { get; }
|
||||||
|
public string OutputFile { get; }
|
||||||
|
public double Start { get; }
|
||||||
|
public double Length { get; }
|
||||||
|
public string[] Passthrough { get; }
|
||||||
|
|
||||||
|
public State(SingleTask job)
|
||||||
|
{
|
||||||
|
InputFile = job.Job.InputFile;
|
||||||
|
OutputFile = job.OutputFileName;
|
||||||
|
Start = job.SegmentStart;
|
||||||
|
Length = job.SegmentLength;
|
||||||
|
Passthrough = job.Job.Passthrough;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public PassthroughSplitter(int segmentNo, ILogger logger)
|
||||||
|
: base(logger, segmentNo)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public IFrameProcessingState InitSegment(SingleTask job, CancellationToken token)
|
||||||
|
{
|
||||||
|
var state = new State(job);
|
||||||
|
state.EncodeProcess = StartEncode(job);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FrameProcessingResult GetNextProcessedFrame(IFrameProcessingState processorState, CancellationToken token)
|
||||||
|
{
|
||||||
|
return new FrameProcessingResult(null, [], null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FinishSegment(IFrameProcessingState processorState)
|
||||||
|
{
|
||||||
|
var state = (State)processorState;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (state.EncodeProcess != null && !state.EncodeProcess.HasExited)
|
||||||
|
state.EncodeProcess.WaitForExit();
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ProcessSegment(
|
||||||
|
SingleTask job,
|
||||||
|
Action<FrameProcessingResult>? onFrameProcessed,
|
||||||
|
CancellationToken token)
|
||||||
|
{
|
||||||
|
var state = (State)InitSegment(job, token);
|
||||||
|
|
||||||
|
var p = state.EncodeProcess;
|
||||||
|
if (p != null)
|
||||||
|
await p.WaitForExitAsync(token);
|
||||||
|
|
||||||
|
FinishSegment(state);
|
||||||
|
|
||||||
|
ClearProgress(job.OutputFileName);
|
||||||
|
|
||||||
|
if (p != null && p.ExitCode != 0)
|
||||||
|
LogError($"Segment {job.OutputFileName} FFmpeg passthrough failed");
|
||||||
|
else
|
||||||
|
LogInfo($"Segment {job.OutputFileName} passthrough completed");
|
||||||
|
}
|
||||||
|
|
||||||
|
private Process StartEncode(SingleTask job)
|
||||||
|
{
|
||||||
|
var inputFile = job.Job.InputFile;
|
||||||
|
var outputFile = job.OutputFileName;
|
||||||
|
var start = job.SegmentStart;
|
||||||
|
var length = job.SegmentLength;
|
||||||
|
|
||||||
|
var args =
|
||||||
|
$"-ss {start.ToString(CultureInfo.InvariantCulture)} " +
|
||||||
|
$"-i \"{inputFile}\" " +
|
||||||
|
$"-t {length.ToString(CultureInfo.InvariantCulture)} " +
|
||||||
|
$"-c copy {string.Join(" ", job.Job.Passthrough)} " +
|
||||||
|
$"\"{outputFile}\" -y";
|
||||||
|
|
||||||
|
var psi = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = "ffmpeg",
|
||||||
|
Arguments = args,
|
||||||
|
RedirectStandardError = true,
|
||||||
|
UseShellExecute = false,
|
||||||
|
CreateNoWindow = true
|
||||||
|
};
|
||||||
|
|
||||||
|
return Process.Start(psi) ?? throw new Exception("Failed to start ffmpeg passthrough.");
|
||||||
|
}
|
||||||
|
}
|
||||||
142
splitter-cli/RotatingSplitter.cs
Normal file
142
splitter-cli/RotatingSplitter.cs
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
using Spectre.Console;
|
||||||
|
|
||||||
|
namespace splitter;
|
||||||
|
|
||||||
|
public sealed class RotatingSplitter : LoggingBase, ISegmentProcessor
|
||||||
|
{
|
||||||
|
private sealed class State : IFrameProcessingState
|
||||||
|
{
|
||||||
|
public Process? EncodeProcess { get; set; }
|
||||||
|
|
||||||
|
public string InputFile { get; }
|
||||||
|
public string OutputFile { get; }
|
||||||
|
public double Start { get; }
|
||||||
|
public double Length { get; }
|
||||||
|
public int Rotate { get; }
|
||||||
|
public VideoInfo Info { get; }
|
||||||
|
public string[] Passthrough { get; }
|
||||||
|
|
||||||
|
public State(SingleTask job)
|
||||||
|
{
|
||||||
|
InputFile = job.Job.InputFile;
|
||||||
|
OutputFile = job.OutputFileName;
|
||||||
|
Start = job.SegmentStart;
|
||||||
|
Length = job.SegmentLength;
|
||||||
|
Rotate = job.Job.Rotate ?? 0;
|
||||||
|
Info = job.Info;
|
||||||
|
Passthrough = job.Job.Passthrough;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public RotatingSplitter(int segmentNo, ILogger logger)
|
||||||
|
: base(logger, segmentNo)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public IFrameProcessingState InitSegment(SingleTask job, CancellationToken token)
|
||||||
|
{
|
||||||
|
var state = new State(job);
|
||||||
|
state.EncodeProcess = StartEncode(job, state.Info, state.Rotate);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FrameProcessingResult GetNextProcessedFrame(IFrameProcessingState processorState, CancellationToken token)
|
||||||
|
{
|
||||||
|
return new FrameProcessingResult(null, [], null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FinishSegment(IFrameProcessingState processorState)
|
||||||
|
{
|
||||||
|
var state = (State)processorState;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (state.EncodeProcess != null && !state.EncodeProcess.HasExited)
|
||||||
|
state.EncodeProcess.WaitForExit();
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ProcessSegment(
|
||||||
|
SingleTask job,
|
||||||
|
Action<FrameProcessingResult>? onFrameProcessed,
|
||||||
|
CancellationToken token)
|
||||||
|
{
|
||||||
|
DrawProgress(Path.GetFileName(job.OutputFileName), 0, TimeSpan.FromSeconds(10), 60);
|
||||||
|
|
||||||
|
var state = (State)InitSegment(job, token);
|
||||||
|
|
||||||
|
var p = state.EncodeProcess;
|
||||||
|
if (p != null)
|
||||||
|
await p.WaitForExitAsync(token);
|
||||||
|
|
||||||
|
FinishSegment(state);
|
||||||
|
|
||||||
|
ClearProgress(job.OutputFileName);
|
||||||
|
|
||||||
|
if (p != null && p.ExitCode != 0)
|
||||||
|
LogError($"Segment {job.OutputFileName} FFmpeg rotation failed");
|
||||||
|
else
|
||||||
|
LogInfo($"Segment {job.OutputFileName} rotation completed");
|
||||||
|
}
|
||||||
|
|
||||||
|
private Process StartEncode(SingleTask job, VideoInfo info, int rotate)
|
||||||
|
{
|
||||||
|
var inputFile = job.Job.InputFile;
|
||||||
|
var outputFile = job.OutputFileName;
|
||||||
|
var start = job.SegmentStart;
|
||||||
|
var length = job.SegmentLength;
|
||||||
|
|
||||||
|
var rotation = GetRotationFilter(rotate);
|
||||||
|
|
||||||
|
// Rotation only. No SAR/DAR manipulation.
|
||||||
|
var vfArg = $"-vf \"{rotation}\" ";
|
||||||
|
|
||||||
|
var args =
|
||||||
|
$"-ss {start.ToString(CultureInfo.InvariantCulture)} " +
|
||||||
|
$"-i \"{inputFile}\" " +
|
||||||
|
$"-t {length.ToString(CultureInfo.InvariantCulture)} " +
|
||||||
|
vfArg +
|
||||||
|
"-c:v h264_nvenc -preset p4 -b:v 8M -pix_fmt yuv420p " +
|
||||||
|
"-c:a copy " +
|
||||||
|
$"{string.Join(" ", job.Job.Passthrough)} " +
|
||||||
|
$"\"{outputFile}\" -y";
|
||||||
|
|
||||||
|
var psi = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = "ffmpeg",
|
||||||
|
Arguments = args,
|
||||||
|
RedirectStandardError = true,
|
||||||
|
UseShellExecute = false,
|
||||||
|
CreateNoWindow = true
|
||||||
|
};
|
||||||
|
|
||||||
|
var p = Process.Start(psi) ?? throw new Exception("Failed to start ffmpeg encode.");
|
||||||
|
|
||||||
|
// Drain stderr and log
|
||||||
|
_ = Task.Run(() =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string? line;
|
||||||
|
while ((line = p.StandardError.ReadLine()) != null)
|
||||||
|
LogInfo(line);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
});
|
||||||
|
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetRotationFilter(int degrees) =>
|
||||||
|
degrees switch
|
||||||
|
{
|
||||||
|
90 => "transpose=1",
|
||||||
|
180 => "rotate=PI",
|
||||||
|
270 => "transpose=2",
|
||||||
|
_ => "transpose=1"
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -1,279 +0,0 @@
|
|||||||
using System.Diagnostics;
|
|
||||||
using System.Globalization;
|
|
||||||
|
|
||||||
namespace splitter;
|
|
||||||
|
|
||||||
public sealed class SimpleSplitter : LoggingBase, ISegmentProcessor
|
|
||||||
{
|
|
||||||
// ------------------------------------------------------------
|
|
||||||
// Internal state (opaque to caller)
|
|
||||||
// ------------------------------------------------------------
|
|
||||||
|
|
||||||
private sealed class State : IFrameProcessingState
|
|
||||||
{
|
|
||||||
public Process? DecodeProcess { get; set; }
|
|
||||||
public Stream? DecodeStdout { get; set; }
|
|
||||||
|
|
||||||
public string InputFile { get; }
|
|
||||||
public double Start { get; }
|
|
||||||
public double Length { get; }
|
|
||||||
public int? Rotate { get; }
|
|
||||||
public string[] Passthrough { get; }
|
|
||||||
public VideoInfo Info { get; }
|
|
||||||
public bool PlainText { get; }
|
|
||||||
|
|
||||||
public State(SingleTask job)
|
|
||||||
{
|
|
||||||
InputFile = job.Job.InputFile;
|
|
||||||
Start = job.SegmentStart;
|
|
||||||
Length = job.SegmentLength;
|
|
||||||
Rotate = job.Job.Rotate;
|
|
||||||
Passthrough = job.Job.Passthrough;
|
|
||||||
Info = job.Info;
|
|
||||||
PlainText = job.Job.PlainText;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public SimpleSplitter(int segmentNo, ILogger logger)
|
|
||||||
: base(logger, segmentNo)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// InitSegment
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
public IFrameProcessingState InitSegment(SingleTask job, CancellationToken token)
|
|
||||||
{
|
|
||||||
var state = new State(job);
|
|
||||||
|
|
||||||
var decode = StartDecode(job, token);
|
|
||||||
state.DecodeProcess = decode;
|
|
||||||
state.DecodeStdout = decode.StandardOutput.BaseStream;
|
|
||||||
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// GetNextProcessedFrame
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
public FrameProcessingResult GetNextProcessedFrame(IFrameProcessingState processorState, CancellationToken token)
|
|
||||||
{
|
|
||||||
var state = (State)processorState;
|
|
||||||
|
|
||||||
if (state.DecodeStdout == null)
|
|
||||||
return new FrameProcessingResult(null, [], null);
|
|
||||||
|
|
||||||
// SimpleSplitter does not modify frames; it only copies or rotates.
|
|
||||||
// For preview, we decode raw frames and return them as-is.
|
|
||||||
|
|
||||||
// Determine expected frame size
|
|
||||||
var w = state.Info.Width;
|
|
||||||
var h = state.Info.Height;
|
|
||||||
var bytes = w * h * 3;
|
|
||||||
|
|
||||||
var buffer = new byte[bytes];
|
|
||||||
var read = state.DecodeStdout.Read(buffer, 0, bytes);
|
|
||||||
if (read != bytes)
|
|
||||||
return new FrameProcessingResult(null, [], null);
|
|
||||||
|
|
||||||
var mat = new Mat(h, w, MatType.CV_8UC3);
|
|
||||||
System.Runtime.InteropServices.Marshal.Copy(buffer, 0, mat.Data, bytes);
|
|
||||||
|
|
||||||
return new FrameProcessingResult(mat, [], null);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// FinishSegment
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
public void FinishSegment(IFrameProcessingState processorState)
|
|
||||||
{
|
|
||||||
var state = (State)processorState;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (state.DecodeProcess != null && !state.DecodeProcess.HasExited)
|
|
||||||
state.DecodeProcess.Kill(entireProcessTree: true);
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (state.DecodeProcess != null && !state.DecodeProcess.HasExited)
|
|
||||||
state.DecodeProcess.WaitForExit();
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// ProcessSegment (now uses preview API)
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
public async Task ProcessSegment(SingleTask job, Action<FrameProcessingResult>? onFrameProcessed, CancellationToken token)
|
|
||||||
{
|
|
||||||
var state = (State)InitSegment(job, token);
|
|
||||||
|
|
||||||
var encode = StartEncode(job);
|
|
||||||
using var encodeStdin = encode.StandardInput.BaseStream;
|
|
||||||
|
|
||||||
var name = Path.GetFileNameWithoutExtension(job.OutputFileName);
|
|
||||||
var sw = Stopwatch.StartNew();
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
token.ThrowIfCancellationRequested();
|
|
||||||
|
|
||||||
var res = GetNextProcessedFrame(state, token);
|
|
||||||
var frame = res.Image;
|
|
||||||
if (frame == null)
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Write raw frame to encoder
|
|
||||||
var bytes = frame.Width * frame.Height * 3;
|
|
||||||
var buffer = new byte[bytes];
|
|
||||||
System.Runtime.InteropServices.Marshal.Copy(frame.Data, buffer, 0, bytes);
|
|
||||||
encodeStdin.Write(buffer, 0, bytes);
|
|
||||||
|
|
||||||
onFrameProcessed?.Invoke(res);
|
|
||||||
|
|
||||||
frame.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
encodeStdin.Flush();
|
|
||||||
encodeStdin.Close();
|
|
||||||
|
|
||||||
await encode.WaitForExitAsync(token);
|
|
||||||
|
|
||||||
FinishSegment(state);
|
|
||||||
|
|
||||||
ClearProgress(name);
|
|
||||||
|
|
||||||
if (encode.ExitCode != 0)
|
|
||||||
LogError($"Segment {name} FFmpeg encoding failed");
|
|
||||||
else
|
|
||||||
LogInfo($"Segment {name} processing completed");
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// FFmpeg helpers
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
private Process StartDecode(SingleTask job, CancellationToken token)
|
|
||||||
{
|
|
||||||
var ss = job.SegmentStart.ToString("0.###", CultureInfo.InvariantCulture);
|
|
||||||
var t = job.SegmentLength.ToString("0.###", CultureInfo.InvariantCulture);
|
|
||||||
|
|
||||||
var rotate = GetRotationFilter(job.Job.Rotate);
|
|
||||||
var vf = rotate != null ? $"-vf format=bgr24,{rotate}" : "-vf format=bgr24";
|
|
||||||
|
|
||||||
var args =
|
|
||||||
$"-i \"{job.Job.InputFile}\" -ss {ss} -t {t} " +
|
|
||||||
"-an -sn " +
|
|
||||||
$"{vf} " +
|
|
||||||
"-f rawvideo -";
|
|
||||||
|
|
||||||
var psi = new ProcessStartInfo
|
|
||||||
{
|
|
||||||
FileName = "ffmpeg",
|
|
||||||
Arguments = args,
|
|
||||||
RedirectStandardOutput = true,
|
|
||||||
RedirectStandardError = true,
|
|
||||||
UseShellExecute = false,
|
|
||||||
CreateNoWindow = true
|
|
||||||
};
|
|
||||||
|
|
||||||
var p = Process.Start(psi) ?? throw new Exception("Failed to start ffmpeg decode.");
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Process StartEncode(SingleTask job)
|
|
||||||
{
|
|
||||||
var inputFile = job.Job.InputFile;
|
|
||||||
var outputFile = job.OutputFileName;
|
|
||||||
var start = job.SegmentStart;
|
|
||||||
var length = job.SegmentLength;
|
|
||||||
|
|
||||||
var rotation = GetRotationFilter(job.Job.Rotate);
|
|
||||||
|
|
||||||
string args;
|
|
||||||
|
|
||||||
if (rotation == null)
|
|
||||||
{
|
|
||||||
args =
|
|
||||||
$"-ss {start.ToString(CultureInfo.InvariantCulture)} " +
|
|
||||||
$"-i \"{inputFile}\" " +
|
|
||||||
$"-t {length.ToString(CultureInfo.InvariantCulture)} " +
|
|
||||||
$"-c copy {string.Join(" ", job.Job.Passthrough)} " +
|
|
||||||
$"\"{outputFile}\" -y";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var sarArg = "";
|
|
||||||
var darArg = "";
|
|
||||||
|
|
||||||
var sar = job.Info.SampleAspectRatio;
|
|
||||||
if (sar != null)
|
|
||||||
{
|
|
||||||
var sarNum = Convert.ToInt64(job.Info.Sar.X);
|
|
||||||
var sarDen = Convert.ToInt64(job.Info.Sar.Y);
|
|
||||||
|
|
||||||
var w = job.Info.Width;
|
|
||||||
var h = job.Info.Height;
|
|
||||||
|
|
||||||
if (job.Job.Rotate == 90 || job.Job.Rotate == 270)
|
|
||||||
(w, h) = (h, w);
|
|
||||||
|
|
||||||
var darNum = w * sarNum;
|
|
||||||
var darDen = h * sarDen;
|
|
||||||
|
|
||||||
long Gcd(long a, long b)
|
|
||||||
{
|
|
||||||
while (b != 0) (a, b) = (b, a % b);
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
|
|
||||||
var g = Gcd(darNum, darDen);
|
|
||||||
darNum /= g;
|
|
||||||
darDen /= g;
|
|
||||||
|
|
||||||
sarArg = $"-vf \"{rotation},setsar={sarNum}:{sarDen}\" ";
|
|
||||||
darArg = $"-aspect {darNum}:{darDen} ";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
sarArg = $"-vf \"{rotation}\" ";
|
|
||||||
|
|
||||||
args =
|
|
||||||
$"-ss {start.ToString(CultureInfo.InvariantCulture)} " +
|
|
||||||
$"-i \"{inputFile}\" " +
|
|
||||||
$"-t {length.ToString(CultureInfo.InvariantCulture)} " +
|
|
||||||
sarArg + darArg +
|
|
||||||
"-c:v h264_nvenc -preset p4 -b:v 8M -pix_fmt yuv420p " +
|
|
||||||
"-c:a copy " +
|
|
||||||
$"{string.Join(" ", job.Job.Passthrough)} " +
|
|
||||||
$"\"{outputFile}\" -y";
|
|
||||||
}
|
|
||||||
|
|
||||||
var psi = new ProcessStartInfo
|
|
||||||
{
|
|
||||||
FileName = "ffmpeg",
|
|
||||||
Arguments = args,
|
|
||||||
RedirectStandardInput = true,
|
|
||||||
RedirectStandardError = true,
|
|
||||||
UseShellExecute = false,
|
|
||||||
CreateNoWindow = true
|
|
||||||
};
|
|
||||||
|
|
||||||
return Process.Start(psi) ?? throw new Exception("Failed to start ffmpeg encode.");
|
|
||||||
}
|
|
||||||
|
|
||||||
private string? GetRotationFilter(int? degrees) =>
|
|
||||||
degrees switch
|
|
||||||
{
|
|
||||||
90 => "transpose=1",
|
|
||||||
180 => "rotate=PI",
|
|
||||||
270 => "transpose=2",
|
|
||||||
_ => null
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -219,7 +219,7 @@ public sealed class TrackingSplitter : LoggingBase, ISegmentProcessor
|
|||||||
// INTERNAL HELPERS
|
// INTERNAL HELPERS
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
private object CreateFrameState(SingleTask job)
|
private FrameProcessingState CreateFrameState(SingleTask job)
|
||||||
{
|
{
|
||||||
var w = job.Info.Width;
|
var w = job.Info.Width;
|
||||||
var h = job.Info.Height;
|
var h = job.Info.Height;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user