From 092bc88d580df52094c3b7e837b9840b82ce5155 Mon Sep 17 00:00:00 2001 From: unclshura Date: Sun, 30 Aug 2026 10:16:33 +0100 Subject: [PATCH] Loseless split is back. Very fast now. Using key frames. --- splitter-cli/JobProcessor.cs | 4 +- splitter-cli/PassthroughSplitter.cs | 65 ++++++++++++++++++++++++++++- 2 files changed, 66 insertions(+), 3 deletions(-) diff --git a/splitter-cli/JobProcessor.cs b/splitter-cli/JobProcessor.cs index ac59206..0185f7c 100644 --- a/splitter-cli/JobProcessor.cs +++ b/splitter-cli/JobProcessor.cs @@ -79,8 +79,8 @@ public class JobProcessor(ILogger logger) : LoggingBase(logger, 0), IJobProcesso } else { - //processorFactory = i => new PassthroughSplitter(i, _logger); - processorFactory = i => new ReencodeSplitter(i, _logger); + processorFactory = i => new PassthroughSplitter(i, _logger); + //processorFactory = i => new ReencodeSplitter(i, _logger); } var segmentsToUse = predefinedSegments; diff --git a/splitter-cli/PassthroughSplitter.cs b/splitter-cli/PassthroughSplitter.cs index d3d96f9..8a76679 100644 --- a/splitter-cli/PassthroughSplitter.cs +++ b/splitter-cli/PassthroughSplitter.cs @@ -82,8 +82,15 @@ public sealed class PassthroughSplitter : LoggingBase, ISegmentProcessor var start = job.SegmentStart; var length = job.SegmentLength; + // Probe next keyframe packet within a 5-second window + var nextKey = ProbeNextKeyframePacket(inputFile, start); + + // Adjust by subtracting X.XX seconds + const double shiftBack = 0.1; + var seekPos = nextKey > shiftBack ? nextKey - shiftBack : 0.0; + var args = - $"-ss {start.ToString(CultureInfo.InvariantCulture)} " + + $"-ss {seekPos.ToString(CultureInfo.InvariantCulture)} " + $"-i \"{inputFile}\" " + $"-t {length.ToString(CultureInfo.InvariantCulture)} " + $"-c copy {string.Join(" ", job.Job.Passthrough)} " + @@ -100,4 +107,60 @@ public sealed class PassthroughSplitter : LoggingBase, ISegmentProcessor return Process.Start(psi) ?? throw new Exception("Failed to start ffmpeg passthrough."); } + + public static double ProbeNextKeyframePacket(string inputFile, double segmentStart, double window = 11.0) + { + var psi = new ProcessStartInfo + { + FileName = "ffprobe", + Arguments = + "-select_streams v " + + $"-read_intervals \"{(segmentStart-0.1).ToString(CultureInfo.InvariantCulture)}%+{window.ToString(CultureInfo.InvariantCulture)}\" " + + "-skip_frame nokey " + + "-show_packets " + + "-show_entries packet=pts_time,flags " + + "-of compact=p=0 " + + "-v quiet " + + $"\"{inputFile}\"", + RedirectStandardOutput = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + using var p = Process.Start(psi); + var output = p!.StandardOutput.ReadToEnd(); + p.WaitForExit(); + + double best = double.MaxValue; + + foreach (var line in output.Split('\n')) + { + if (string.IsNullOrWhiteSpace(line)) + continue; + + // Example line: + // pts_time=23.520000|flags=K__ + var parts = line.Split('|'); + if (parts.Length < 2) + continue; + + var ptsPart = parts[0].Trim(); // pts_time=23.520000 + var flagsPart = parts[1].Trim(); // flags=K__ + + if (!flagsPart.Contains("K")) + continue; + + var ptsStr = ptsPart.Replace("pts_time=", ""); + if (!double.TryParse(ptsStr, NumberStyles.Float, CultureInfo.InvariantCulture, out var pts)) + continue; + + if ( best == double.MaxValue) + best = pts; + if (pts > segmentStart) + break; + } + + return best == double.MaxValue ? segmentStart : best; + } + }