mirror of
https://github.com/unclshura/splitter.git
synced 2026-06-21 16:12:01 +00:00
Sar/Dar support for all segment processors
This commit is contained in:
parent
23bfdc8452
commit
093c7c7803
@ -26,12 +26,12 @@ public sealed class AutoDecisionService(IThumbnailService _thumbnails, IFileProb
|
||||
|
||||
CalculateCrop(job);
|
||||
}
|
||||
else
|
||||
{
|
||||
var sampler = new VideoRotationSampler(null);
|
||||
job.Rotate = await sampler.DetectRotationAsync(job.InputFile, job.Probe.Duration, token);
|
||||
job.Detect = job.Rotate == 0 ? null : "body";
|
||||
}
|
||||
//else
|
||||
//{
|
||||
// var sampler = new VideoRotationSampler(null);
|
||||
// job.Rotate = await sampler.DetectRotationAsync(job.InputFile, job.Probe.Duration, token);
|
||||
// job.Detect = job.Rotate == 0 ? null : "body";
|
||||
//}
|
||||
|
||||
_log.LogInfo(job.ToString());
|
||||
}
|
||||
|
||||
@ -7,40 +7,77 @@ public class SimpleSplitter(int segmentNo, ILogger logger) : LoggingBase(logger,
|
||||
{
|
||||
public async Task ProcessSegment(SingleTask job, CancellationToken token)
|
||||
{
|
||||
string inputFile = job.Job.InputFile;
|
||||
string outputFile = job.OutputFileName;
|
||||
double start = job.SegmentStart;
|
||||
double length = job.SegmentLength;
|
||||
int videoWidth = job.Info.Width;
|
||||
int videoHeight = job.Info.Height;
|
||||
double fps = job.Info.Fps;
|
||||
string[] ffmpegPassthroughParameters = job.Job.Passthrough;
|
||||
|
||||
var pass = ffmpegPassthroughParameters.Length > 0 ? string.Join(" ", ffmpegPassthroughParameters) : "";
|
||||
string inputFile = job.Job.InputFile;
|
||||
string outputFile = job.OutputFileName;
|
||||
double start = job.SegmentStart;
|
||||
double length = job.SegmentLength;
|
||||
|
||||
var rotation = GetRotationFilter(job.Job.Rotate);
|
||||
|
||||
string args;
|
||||
var rotation = GetRotationFilter(job.Job.Rotate);
|
||||
|
||||
if (rotation == null)
|
||||
{
|
||||
// Copy path: keep original SAR/DAR exactly as in source
|
||||
args =
|
||||
$"-ss {start.ToString(CultureInfo.InvariantCulture)} " +
|
||||
$"-i \"{inputFile}\" " +
|
||||
$"-t {length.ToString(CultureInfo.InvariantCulture)} " +
|
||||
$"-c copy {pass} \"{outputFile}\" -y";
|
||||
$"-c copy {string.Join(" ", job.Job.Passthrough)} " +
|
||||
$"\"{outputFile}\" -y";
|
||||
}
|
||||
else
|
||||
{
|
||||
// Rotation → must re-encode
|
||||
var sarArg = "";
|
||||
var darArg = "";
|
||||
|
||||
var sar = job.Info.SampleAspectRatio; // e.g. "4:3"
|
||||
if (sar != null)
|
||||
{
|
||||
// Rotation path: must re-encode and recompute DAR
|
||||
|
||||
long sarNum = Convert.ToInt64(job.Info.Sar.X);
|
||||
long sarDen = Convert.ToInt64(job.Info.Sar.Y);
|
||||
|
||||
// After rotation, width/height swap
|
||||
int w = job.Info.Width;
|
||||
int h = job.Info.Height;
|
||||
|
||||
if (job.Job.Rotate == 90 || job.Job.Rotate == 270)
|
||||
{
|
||||
(w, h) = (h, w);
|
||||
}
|
||||
|
||||
// Compute DAR = (w * sarNum) : (h * sarDen)
|
||||
var darNum = w * sarNum;
|
||||
var darDen = h * sarDen;
|
||||
|
||||
// Reduce fraction
|
||||
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)} " +
|
||||
$"-vf \"{rotation}\" " +
|
||||
sarArg + darArg +
|
||||
"-c:v h264_nvenc -preset p4 -b:v 8M -pix_fmt yuv420p " +
|
||||
"-c:a copy " +
|
||||
$"{pass} \"{outputFile}\" -y";
|
||||
$"{string.Join(" ", job.Job.Passthrough)} " +
|
||||
$"\"{outputFile}\" -y";
|
||||
}
|
||||
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = "ffmpeg",
|
||||
@ -56,7 +93,7 @@ public class SimpleSplitter(int segmentNo, ILogger logger) : LoggingBase(logger,
|
||||
await ShowFFMpegProgress(length, proc, name, token);
|
||||
|
||||
await proc.WaitForExitAsync(token);
|
||||
|
||||
|
||||
ClearProgress(name);
|
||||
|
||||
if (proc.ExitCode != 0)
|
||||
@ -65,6 +102,7 @@ public class SimpleSplitter(int segmentNo, ILogger logger) : LoggingBase(logger,
|
||||
LogInfo($"Segment {name} processing completed");
|
||||
}
|
||||
|
||||
|
||||
string? GetRotationFilter(int? degrees) =>
|
||||
degrees switch
|
||||
{
|
||||
@ -74,6 +112,20 @@ public class SimpleSplitter(int segmentNo, ILogger logger) : LoggingBase(logger,
|
||||
_ => null
|
||||
};
|
||||
|
||||
private static long Gcd(long a, long b)
|
||||
{
|
||||
a = Math.Abs(a);
|
||||
b = Math.Abs(b);
|
||||
|
||||
while (b != 0)
|
||||
{
|
||||
long t = b;
|
||||
b = a % b;
|
||||
a = t;
|
||||
}
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
private async Task ShowFFMpegProgress(double length, Process proc, string name, CancellationToken token)
|
||||
{
|
||||
|
||||
@ -68,7 +68,7 @@ public class TrackingSplitter : LoggingBase, ISegmentProcessor, IDisposable
|
||||
length,
|
||||
encWidth,
|
||||
encHeight,
|
||||
fps,
|
||||
job.Info,
|
||||
ffmpegPassthroughParameters,
|
||||
job.Job.PlainText,
|
||||
token);
|
||||
@ -231,17 +231,36 @@ public class TrackingSplitter : LoggingBase, ISegmentProcessor, IDisposable
|
||||
string outputFile,
|
||||
double start,
|
||||
double length,
|
||||
int width,
|
||||
int height,
|
||||
double fps,
|
||||
int width, int height,
|
||||
VideoInfo info,
|
||||
string[] passthrough,
|
||||
bool plainText,
|
||||
CancellationToken token)
|
||||
{
|
||||
var pass = passthrough.Length > 0 ? string.Join(" ", passthrough) : "";
|
||||
var fpsStr = fps.ToString("0.###", CultureInfo.InvariantCulture);
|
||||
var fpsStr = info.Fps.ToString("0.###", CultureInfo.InvariantCulture);
|
||||
var ss = start.ToString("0.###", CultureInfo.InvariantCulture);
|
||||
var t = length.ToString("0.###", CultureInfo.InvariantCulture);
|
||||
var sarArg = !string.IsNullOrWhiteSpace(info.SampleAspectRatio)
|
||||
? $"-vf setsar={info.SampleAspectRatio} "
|
||||
: "";
|
||||
|
||||
string darArg = "";
|
||||
|
||||
if (info.Sar is { } s)
|
||||
{
|
||||
// compute DAR from output size and SAR
|
||||
var darNum = width * s.X;
|
||||
var darDen = height * s.Y;
|
||||
|
||||
// clamp to int and reduce
|
||||
int dn = (int)Math.Min(int.MaxValue, Math.Max(int.MinValue, darNum));
|
||||
int dd = (int)Math.Min(int.MaxValue, Math.Max(int.MinValue, darDen));
|
||||
ReduceFraction(ref dn, ref dd);
|
||||
|
||||
if (dn > 0 && dd > 0)
|
||||
darArg = $"-aspect {dn}:{dd} ";
|
||||
}
|
||||
|
||||
var args =
|
||||
"-y " +
|
||||
@ -249,6 +268,7 @@ public class TrackingSplitter : LoggingBase, ISegmentProcessor, IDisposable
|
||||
$"-ss {ss} -i \"{inputFile}\" " +
|
||||
"-map 0:v:0 -map 1:a:0? -shortest " +
|
||||
"-c:v h264_nvenc -preset p4 -b:v 8M -pix_fmt yuv420p " +
|
||||
sarArg + darArg +
|
||||
"-c:a copy " +
|
||||
pass + $" \"{outputFile}\"";
|
||||
|
||||
@ -289,6 +309,26 @@ public class TrackingSplitter : LoggingBase, ISegmentProcessor, IDisposable
|
||||
|
||||
// ---------- helpers ----------
|
||||
|
||||
private static void ReduceFraction(ref int num, ref int den)
|
||||
{
|
||||
int Gcd(int a, int b)
|
||||
{
|
||||
while (b != 0)
|
||||
{
|
||||
var t = b;
|
||||
b = a % b;
|
||||
a = t;
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
var g = Gcd(Math.Abs(num), Math.Abs(den));
|
||||
if (g > 1)
|
||||
{
|
||||
num /= g;
|
||||
den /= g;
|
||||
}
|
||||
}
|
||||
private static async Task<int> ReadExact(Stream s, byte[] buffer, int offset, int count, CancellationToken token)
|
||||
{
|
||||
var total = 0;
|
||||
|
||||
@ -87,29 +87,7 @@ public static class ProbeVideo
|
||||
|
||||
var bitrate = stream?.Bit_rate ?? 0.0;
|
||||
|
||||
var sar = ParseAspectRatio(stream?.Sample_aspect_ratio);
|
||||
var dar = ParseAspectRatio(stream?.Display_aspect_ratio);
|
||||
|
||||
return new VideoInfo(duration, width, height, fps, bitrate, sar, dar);
|
||||
}
|
||||
|
||||
private static Point2f ParseAspectRatio(string? sar)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(sar))
|
||||
return new Point2f(1.0f, 1.0f);
|
||||
|
||||
var parts = sar.Split(':');
|
||||
if (parts.Length != 2)
|
||||
return new(1.0f, 1.0f);
|
||||
|
||||
if (float.TryParse(parts[0], NumberStyles.Float, CultureInfo.InvariantCulture, out var num) &&
|
||||
float.TryParse(parts[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var den) &&
|
||||
den != 0)
|
||||
{
|
||||
return new(num, den);
|
||||
}
|
||||
|
||||
return new(1.0f, 1.0f);
|
||||
return new VideoInfo(duration, width, height, fps, bitrate, stream?.Sample_aspect_ratio, stream?.Display_aspect_ratio);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
namespace splitter.probe;
|
||||
using System.Globalization;
|
||||
|
||||
namespace splitter.probe;
|
||||
|
||||
public record VideoInfo(
|
||||
double Duration,
|
||||
@ -6,7 +8,30 @@ public record VideoInfo(
|
||||
int Height,
|
||||
double Fps,
|
||||
double Bitrate,
|
||||
Point2f Sar,
|
||||
Point2f Dar,
|
||||
string? SampleAspectRatio,
|
||||
string? DisplayAspectRatio,
|
||||
int Rotation = 0
|
||||
);
|
||||
)
|
||||
{
|
||||
public Point2f Sar => ParseAspectRatio(SampleAspectRatio);
|
||||
public Point2f Dar => ParseAspectRatio(DisplayAspectRatio);
|
||||
|
||||
private static Point2f ParseAspectRatio(string? sar)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(sar))
|
||||
return new Point2f(1.0f, 1.0f);
|
||||
|
||||
var parts = sar.Split(':');
|
||||
if (parts.Length != 2)
|
||||
return new(1.0f, 1.0f);
|
||||
|
||||
if (float.TryParse(parts[0], NumberStyles.Float, CultureInfo.InvariantCulture, out var num) &&
|
||||
float.TryParse(parts[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var den) &&
|
||||
den != 0)
|
||||
{
|
||||
return new(num, den);
|
||||
}
|
||||
|
||||
return new(1.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user