diff --git a/FFMpegCore.Test/VideoTest.cs b/FFMpegCore.Test/VideoTest.cs
index 5071a48..1de87e0 100644
--- a/FFMpegCore.Test/VideoTest.cs
+++ b/FFMpegCore.Test/VideoTest.cs
@@ -16,6 +16,22 @@ namespace FFMpegCore.Test
public class VideoTest
{
private const int BaseTimeoutMilliseconds = 15_000;
+ private string _segmentPathSource = "";
+
+ [TestInitialize]
+ public void Setup()
+ {
+ _segmentPathSource = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}-");
+ }
+
+ [TestCleanup]
+ public void Cleanup()
+ {
+ foreach (var file in Directory.EnumerateFiles(Path.GetDirectoryName(_segmentPathSource), Path.GetFileName(_segmentPathSource) + "*"))
+ {
+ File.Delete(file);
+ }
+ }
[TestMethod, Timeout(BaseTimeoutMilliseconds)]
public void Video_ToOGV()
@@ -895,5 +911,95 @@ public async Task Video_Cancel_CancellationToken_Async_With_Timeout()
Assert.AreEqual("h264", outputInfo.PrimaryVideoStream.CodecName);
Assert.AreEqual("aac", outputInfo.PrimaryAudioStream!.CodecName);
}
+
+ [TestMethod, Timeout(BaseTimeoutMilliseconds)]
+ public void Video_Segmented_File_Output()
+ {
+ using var input = File.OpenRead(TestResources.WebmVideo);
+ var success = FFMpegArguments
+ .FromPipeInput(new StreamPipeSource(input))
+ .OutPutToSegmentedFiles(
+ new SegmentArgument($"{_segmentPathSource}%Y-%m-%d_%H-%M-%S.mkv", true, segmentOptions => segmentOptions
+ .Strftime(true)
+ .Wrap(-1)
+ .Time(60)
+ .ResetTimeStamps(true)),
+ options => options
+ .CopyChannel()
+ .WithVideoCodec("h264")
+ .ForceFormat("matroska")
+ .WithConstantRateFactor(21)
+ .WithVideoBitrate(3000)
+ .WithFastStart()
+ .WithVideoFilters(filterOptions => filterOptions
+ .Scale(VideoSize.Hd)
+ .DrawText(DrawTextOptions.Create(@"'%{localtime}.%{eif\:1M*t-1K*trunc(t*1K)\:d\:3}'", @"C:/Users/yan.gauthier/AppData/Local/Microsoft/Windows/Fonts/Roboto-Regular.ttf")
+ .WithParameter("fontcolor", "yellow")
+ .WithParameter("fontsize", "40")
+ .WithParameter("x", "(w-text_w)")
+ .WithParameter("y", "(h - text_h)")
+ .WithParameter("rate", "19")
+ )
+ )
+ )
+ .CancellableThrough(new CancellationTokenSource().Token, 8000)
+ .ProcessSynchronously(false);
+ Assert.IsTrue(success);
+ }
+
+ [TestMethod, Timeout(BaseTimeoutMilliseconds)]
+ public void Video_MultiOutput_With_Segmented_File_Output()
+ {
+ using var input = File.OpenRead(TestResources.WebmVideo);
+ var success = FFMpegArguments
+ .FromPipeInput(new StreamPipeSource(input))
+ .MultiOutput(args => args
+ .OutputToFile($"{_segmentPathSource}2", true, options => options
+ .CopyChannel()
+ .WithVideoCodec("mjpeg")
+ .ForceFormat("matroska")
+ .WithConstantRateFactor(21)
+ .WithVideoBitrate(4000)
+ .WithFastStart()
+ .WithVideoFilters(filterOptions => filterOptions
+ .Scale(VideoSize.Hd)
+ .DrawText(DrawTextOptions.Create(@"'%{localtime}.%{eif\:1M*t-1K*trunc(t*1K)\:d\:3}'", @"C:/Users/yan.gauthier/AppData/Local/Microsoft/Windows/Fonts/Roboto-Regular.ttf")
+ .WithParameter("fontcolor", "yellow")
+ .WithParameter("fontsize", "40")
+ .WithParameter("x", "(w-text_w)")
+ .WithParameter("y", "(h - text_h)")
+ .WithParameter("rate", "19")
+ )
+ )
+ )
+ .OutPutToSegmentedFiles(
+ new SegmentArgument($"{_segmentPathSource}%Y-%m-%d_%H-%M-%S.mkv", true, segmentOptions => segmentOptions
+ .Strftime(true)
+ .Wrap(-1)
+ .Time(60)
+ .ResetTimeStamps(true)),
+ options => options
+ .CopyChannel()
+ .WithVideoCodec("h264")
+ .ForceFormat("matroska")
+ .WithConstantRateFactor(21)
+ .WithVideoBitrate(3000)
+ .WithFastStart()
+ .WithVideoFilters(filterOptions => filterOptions
+ .Scale(VideoSize.Hd)
+ .DrawText(DrawTextOptions.Create(@"'%{localtime}.%{eif\:1M*t-1K*trunc(t*1K)\:d\:3}'", @"C:/Users/yan.gauthier/AppData/Local/Microsoft/Windows/Fonts/Roboto-Regular.ttf")
+ .WithParameter("fontcolor", "yellow")
+ .WithParameter("fontsize", "40")
+ .WithParameter("x", "(w-text_w)")
+ .WithParameter("y", "(h - text_h)")
+ .WithParameter("rate", "19")
+ )
+ )
+ )
+ )
+ .CancellableThrough(new CancellationTokenSource().Token, 8000)
+ .ProcessSynchronously(false);
+ Assert.IsTrue(success);
+ }
}
}
diff --git a/FFMpegCore/FFMpeg/Arguments/OutputSegmentArgument.cs b/FFMpegCore/FFMpeg/Arguments/OutputSegmentArgument.cs
new file mode 100644
index 0000000..f6113cb
--- /dev/null
+++ b/FFMpegCore/FFMpeg/Arguments/OutputSegmentArgument.cs
@@ -0,0 +1,92 @@
+using FFMpegCore.Exceptions;
+
+namespace FFMpegCore.Arguments
+{
+ ///
+ /// Represents output parameter
+ ///
+ public class OutputSegmentArgument : IOutputArgument
+ {
+ public readonly string SegmentPattern;
+ public readonly bool Overwrite;
+ public readonly SegmentArgumentOptions Options;
+
+ public OutputSegmentArgument(SegmentArgument segmentArgument)
+ {
+ SegmentPattern = segmentArgument.SegmentPattern;
+ Overwrite = segmentArgument.Overwrite;
+
+ var segmentArgumentobj = new SegmentArgumentOptions();
+ segmentArgument.Options?.Invoke(segmentArgumentobj);
+ Options = segmentArgumentobj;
+ }
+
+ public void Pre()
+ {
+ if (int.TryParse(Options.Arguments.FirstOrDefault(x => x.Key == "segment_time").Value, out var result) && result < 1)
+ {
+ throw new FFMpegException(FFMpegExceptionType.Process, "Parameter SegmentTime cannot be negative or equal to zero");
+ }
+
+ if (Options.Arguments.FirstOrDefault(x => x.Key == "segment_time").Value == "0")
+
+ {
+ throw new FFMpegException(FFMpegExceptionType.Process, "Parameter SegmentWrap cannot equal to zero");
+ }
+ }
+ public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask;
+ public void Post()
+ {
+ }
+
+ public string Text => GetText();
+
+ private string GetText()
+ {
+ var arguments = Options.Arguments
+ .Where(arg => !string.IsNullOrWhiteSpace(arg.Value) && !string.IsNullOrWhiteSpace(arg.Key))
+ .Select(arg =>
+ {
+ return arg.Value;
+ });
+
+ return $"-f segment {string.Join(" ", arguments)} \"{SegmentPattern}\"{(Overwrite ? " -y" : string.Empty)}";
+ }
+ }
+
+ public interface ISegmentArgument
+ {
+ public string Key { get; }
+ public string Value { get; }
+ }
+
+ public class SegmentArgumentOptions
+ {
+ public List Arguments { get; } = new();
+
+ public SegmentArgumentOptions ResetTimeStamps(bool resetTimestamps = true) => WithArgument(new SegmentResetTimeStampsArgument(resetTimestamps));
+ public SegmentArgumentOptions Strftime(bool enable = false) => WithArgument(new SegmentStrftimeArgument(enable));
+ public SegmentArgumentOptions Time(int time = 60) => WithArgument(new SegmentTimeArgument(time));
+ public SegmentArgumentOptions Wrap(int limit = -1) => WithArgument(new SegmentWrapArgument(limit));
+ public SegmentArgumentOptions WithCustomArgument(string argument) => WithArgument(new SegmentCustomArgument(argument));
+ private SegmentArgumentOptions WithArgument(ISegmentArgument argument)
+ {
+ Arguments.Add(argument);
+ return this;
+ }
+ }
+
+ public class SegmentArgument
+ {
+ public readonly string SegmentPattern;
+ public readonly bool Overwrite;
+ public readonly Action Options;
+
+ public SegmentArgument(string segmentPattern, bool overwrite, Action options)
+ {
+ SegmentPattern = segmentPattern;
+ Overwrite = overwrite;
+ Options = options;
+ }
+ }
+}
diff --git a/FFMpegCore/FFMpeg/Arguments/SegmentCustomArgument.cs b/FFMpegCore/FFMpeg/Arguments/SegmentCustomArgument.cs
new file mode 100644
index 0000000..359e529
--- /dev/null
+++ b/FFMpegCore/FFMpeg/Arguments/SegmentCustomArgument.cs
@@ -0,0 +1,14 @@
+namespace FFMpegCore.Arguments
+{
+ public class SegmentCustomArgument : ISegmentArgument
+ {
+ public readonly string Argument;
+
+ public SegmentCustomArgument(string argument)
+ {
+ Argument = argument;
+ }
+ public string Key => "custom";
+ public string Value => Argument ?? string.Empty;
+ }
+}
diff --git a/FFMpegCore/FFMpeg/Arguments/SegmentResetTimestampsArgument.cs b/FFMpegCore/FFMpeg/Arguments/SegmentResetTimestampsArgument.cs
new file mode 100644
index 0000000..f9a048f
--- /dev/null
+++ b/FFMpegCore/FFMpeg/Arguments/SegmentResetTimestampsArgument.cs
@@ -0,0 +1,20 @@
+namespace FFMpegCore.Arguments
+{
+ ///
+ /// Represents reset_timestamps parameter
+ ///
+ public class SegmentResetTimeStampsArgument : ISegmentArgument
+ {
+ public readonly bool ResetTimestamps;
+ ///
+ /// Represents reset_timestamps parameter
+ ///
+ /// true if files timestamps are to be reset
+ public SegmentResetTimeStampsArgument(bool resetTimestamps)
+ {
+ ResetTimestamps = resetTimestamps;
+ }
+ public string Key { get; } = "reset_timestamps";
+ public string Value => ResetTimestamps ? $"-reset_timestamps 1" : string.Empty;
+ }
+}
diff --git a/FFMpegCore/FFMpeg/Arguments/SegmentStrftimeArgument.cs b/FFMpegCore/FFMpeg/Arguments/SegmentStrftimeArgument.cs
new file mode 100644
index 0000000..b50552a
--- /dev/null
+++ b/FFMpegCore/FFMpeg/Arguments/SegmentStrftimeArgument.cs
@@ -0,0 +1,20 @@
+namespace FFMpegCore.Arguments
+{
+ ///
+ /// Use the strftime function to define the name of the new segments to write. If this is selected, the output segment name must contain a strftime function template. Default value is 0.
+ ///
+ public class SegmentStrftimeArgument : ISegmentArgument
+ {
+ public readonly bool Enable;
+ ///
+ /// Use the strftime function to define the name of the new segments to write. If this is selected, the output segment name must contain a strftime function template. Default value is 0.
+ ///
+ /// true to enable strftime
+ public SegmentStrftimeArgument(bool enable)
+ {
+ Enable = enable;
+ }
+ public string Key { get; } = "strftime";
+ public string Value => Enable ? $"-strftime 1" : string.Empty;
+ }
+}
diff --git a/FFMpegCore/FFMpeg/Arguments/SegmentTimeArgument.cs b/FFMpegCore/FFMpeg/Arguments/SegmentTimeArgument.cs
new file mode 100644
index 0000000..5a42e29
--- /dev/null
+++ b/FFMpegCore/FFMpeg/Arguments/SegmentTimeArgument.cs
@@ -0,0 +1,20 @@
+namespace FFMpegCore.Arguments
+{
+ ///
+ /// Represents segment_time parameter
+ ///
+ public class SegmentTimeArgument : ISegmentArgument
+ {
+ public readonly int Time;
+ ///
+ /// Represents segment_time parameter
+ ///
+ /// time in seconds of the segment
+ public SegmentTimeArgument(int time)
+ {
+ Time = time;
+ }
+ public string Key { get; } = "segment_time";
+ public string Value => Time <= 0 ? string.Empty : $"-segment_time {Time}";
+ }
+}
diff --git a/FFMpegCore/FFMpeg/Arguments/SegmentWrapArgument.cs b/FFMpegCore/FFMpeg/Arguments/SegmentWrapArgument.cs
new file mode 100644
index 0000000..be38208
--- /dev/null
+++ b/FFMpegCore/FFMpeg/Arguments/SegmentWrapArgument.cs
@@ -0,0 +1,20 @@
+namespace FFMpegCore.Arguments
+{
+ ///
+ /// Represents segment_wrap parameter
+ ///
+ public class SegmentWrapArgument : ISegmentArgument
+ {
+ public readonly int Limit;
+ ///
+ /// Represents segment_wrap parameter
+ ///
+ /// limit value after which segment index will wrap around
+ public SegmentWrapArgument(int limit)
+ {
+ Limit = limit;
+ }
+ public string Key { get; } = "segment_wrap";
+ public string Value => Limit <= 0 ? string.Empty : $"-segment_wrap {Limit}";
+ }
+}
diff --git a/FFMpegCore/FFMpeg/FFMpegArguments.cs b/FFMpegCore/FFMpeg/FFMpegArguments.cs
index cfc6d9d..4cb2c19 100644
--- a/FFMpegCore/FFMpeg/FFMpegArguments.cs
+++ b/FFMpegCore/FFMpeg/FFMpegArguments.cs
@@ -61,7 +61,7 @@ private FFMpegArguments WithInput(IInputArgument inputArgument, Action? addArguments = null) => ToProcessor(new OutputUrlArgument(uri), addArguments);
public FFMpegArgumentProcessor OutputToUrl(Uri uri, Action? addArguments = null) => ToProcessor(new OutputUrlArgument(uri.ToString()), addArguments);
public FFMpegArgumentProcessor OutputToPipe(IPipeSink reader, Action? addArguments = null) => ToProcessor(new OutputPipeArgument(reader), addArguments);
-
+ public FFMpegArgumentProcessor OutPutToSegmentedFiles(SegmentArgument segmentArgument, Action? addArguments = null) => ToProcessor(new OutputSegmentArgument(segmentArgument), addArguments);
private FFMpegArgumentProcessor ToProcessor(IOutputArgument argument, Action? addArguments)
{
var args = new FFMpegArgumentOptions();
diff --git a/FFMpegCore/FFMpeg/FFMpegMultiOutputOptions.cs b/FFMpegCore/FFMpeg/FFMpegMultiOutputOptions.cs
index 594413b..b0d5359 100644
--- a/FFMpegCore/FFMpeg/FFMpegMultiOutputOptions.cs
+++ b/FFMpegCore/FFMpeg/FFMpegMultiOutputOptions.cs
@@ -16,7 +16,7 @@ public class FFMpegMultiOutputOptions
public FFMpegMultiOutputOptions OutputToUrl(Uri uri, Action? addArguments = null) => AddOutput(new OutputUrlArgument(uri.ToString()), addArguments);
public FFMpegMultiOutputOptions OutputToPipe(IPipeSink reader, Action? addArguments = null) => AddOutput(new OutputPipeArgument(reader), addArguments);
-
+ public FFMpegMultiOutputOptions OutPutToSegmentedFiles(SegmentArgument segmentArgument, Action? addArguments = null) => AddOutput(new OutputSegmentArgument(segmentArgument), addArguments);
public FFMpegMultiOutputOptions AddOutput(IOutputArgument argument, Action? addArguments)
{
var args = new FFMpegArgumentOptions();