diff --git a/FFMpegCore.Examples/FFMpegCore.Examples.csproj b/FFMpegCore.Examples/FFMpegCore.Examples.csproj
index 68e7b5c..347607f 100644
--- a/FFMpegCore.Examples/FFMpegCore.Examples.csproj
+++ b/FFMpegCore.Examples/FFMpegCore.Examples.csproj
@@ -6,6 +6,7 @@
+
diff --git a/FFMpegCore.Examples/Program.cs b/FFMpegCore.Examples/Program.cs
index a718a21..ea343f2 100644
--- a/FFMpegCore.Examples/Program.cs
+++ b/FFMpegCore.Examples/Program.cs
@@ -5,7 +5,7 @@
using FFMpegCore;
using FFMpegCore.Enums;
using FFMpegCore.Pipes;
-using FFMpegCore.Extend;
+using FFMpegCore.Extensions.System.Drawing.Common;
var inputPath = "/path/to/input";
var outputPath = "/path/to/output";
@@ -34,7 +34,7 @@
{
// process the snapshot in-memory and use the Bitmap directly
- var bitmap = FFMpeg.Snapshot(inputPath, new Size(200, 400), TimeSpan.FromMinutes(1));
+ var bitmap = FFMpegImage.Snapshot(inputPath, new Size(200, 400), TimeSpan.FromMinutes(1));
// or persists the image on the drive
FFMpeg.Snapshot(inputPath, outputPath, new Size(200, 400), TimeSpan.FromMinutes(1));
@@ -61,7 +61,7 @@ await FFMpegArguments
}
{
- FFMpeg.JoinImageSequence(@"..\joined_video.mp4", frameRate: 1,
+ FFMpegImage.JoinImageSequence(@"..\joined_video.mp4", frameRate: 1,
ImageInfo.FromPath(@"..\1.png"),
ImageInfo.FromPath(@"..\2.png"),
ImageInfo.FromPath(@"..\3.png")
@@ -83,7 +83,7 @@ await FFMpegArguments
var inputImagePath = "/path/to/input/image";
{
- FFMpeg.PosterWithAudio(inputPath, inputAudioPath, outputPath);
+ FFMpegImage.PosterWithAudio(inputPath, inputAudioPath, outputPath);
// or
var image = Image.FromFile(inputImagePath);
image.AddAudio(inputAudioPath, outputPath);
diff --git a/FFMpegCore/Extend/BitmapExtensions.cs b/FFMpegCore.Extensions.System.Drawing.Common/BitmapExtensions.cs
similarity index 91%
rename from FFMpegCore/Extend/BitmapExtensions.cs
rename to FFMpegCore.Extensions.System.Drawing.Common/BitmapExtensions.cs
index e2f5505..e549580 100644
--- a/FFMpegCore/Extend/BitmapExtensions.cs
+++ b/FFMpegCore.Extensions.System.Drawing.Common/BitmapExtensions.cs
@@ -2,7 +2,7 @@
using System.Drawing;
using System.IO;
-namespace FFMpegCore.Extend
+namespace FFMpegCore.Extensions.System.Drawing.Common
{
public static class BitmapExtensions
{
diff --git a/FFMpegCore/Extend/BitmapVideoFrameWrapper.cs b/FFMpegCore.Extensions.System.Drawing.Common/BitmapVideoFrameWrapper.cs
similarity index 98%
rename from FFMpegCore/Extend/BitmapVideoFrameWrapper.cs
rename to FFMpegCore.Extensions.System.Drawing.Common/BitmapVideoFrameWrapper.cs
index 2222db6..2259fea 100644
--- a/FFMpegCore/Extend/BitmapVideoFrameWrapper.cs
+++ b/FFMpegCore.Extensions.System.Drawing.Common/BitmapVideoFrameWrapper.cs
@@ -7,7 +7,7 @@
using System.Threading.Tasks;
using FFMpegCore.Pipes;
-namespace FFMpegCore.Extend
+namespace FFMpegCore.Extensions.System.Drawing.Common
{
public class BitmapVideoFrameWrapper : IVideoFrame, IDisposable
{
diff --git a/FFMpegCore.Extensions.System.Drawing.Common/FFMpegCore.Extensions.System.Drawing.Common.csproj b/FFMpegCore.Extensions.System.Drawing.Common/FFMpegCore.Extensions.System.Drawing.Common.csproj
new file mode 100644
index 0000000..beeb939
--- /dev/null
+++ b/FFMpegCore.Extensions.System.Drawing.Common/FFMpegCore.Extensions.System.Drawing.Common.csproj
@@ -0,0 +1,28 @@
+
+
+
+ en
+ https://github.com/rosenbjerg/FFMpegCore
+ https://github.com/rosenbjerg/FFMpegCore
+
+ Image extension for FFMpegCore, using System.Common.Drawing
+
+
+ 8
+ 4.0.0.0
+ 4.0.0
+ MIT
+ Malte Rosenbjerg, Vlad Jerca, Max Bagryantsev
+ ffmpeg ffprobe convert video audio mediafile resize analyze muxing
+ GitHub
+ true
+ enable
+ netstandard2.0
+
+
+
+
+
+
+
+
diff --git a/FFMpegCore.Extensions.System.Drawing.Common/FFMpegImage.cs b/FFMpegCore.Extensions.System.Drawing.Common/FFMpegImage.cs
new file mode 100644
index 0000000..19fd16e
--- /dev/null
+++ b/FFMpegCore.Extensions.System.Drawing.Common/FFMpegImage.cs
@@ -0,0 +1,134 @@
+using System;
+using System.Drawing;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using FFMpegCore.Enums;
+using FFMpegCore.Helpers;
+using FFMpegCore.Pipes;
+
+namespace FFMpegCore.Extensions.System.Drawing.Common
+{
+ public static class FFMpegImage
+ {
+ public static void ConversionSizeExceptionCheck(Image image)
+ => FFMpegHelper.ConversionSizeExceptionCheck(image.Size.Width, image.Size.Height);
+
+ ///
+ /// Converts an image sequence to a video.
+ ///
+ /// Output video file.
+ /// FPS
+ /// Image sequence collection
+ /// Output video information.
+ public static bool JoinImageSequence(string output, double frameRate = 30, params ImageInfo[] images)
+ {
+ var tempFolderName = Path.Combine(GlobalFFOptions.Current.TemporaryFilesFolder, Guid.NewGuid().ToString());
+ var temporaryImageFiles = images.Select((imageInfo, index) =>
+ {
+ using var image = Image.FromFile(imageInfo.FullName);
+ FFMpegHelper.ConversionSizeExceptionCheck(image);
+ var destinationPath = Path.Combine(tempFolderName, $"{index.ToString().PadLeft(9, '0')}{imageInfo.Extension}");
+ Directory.CreateDirectory(tempFolderName);
+ File.Copy(imageInfo.FullName, destinationPath);
+ return destinationPath;
+ }).ToArray();
+
+ var firstImage = images.First();
+ try
+ {
+ return FFMpegArguments
+ .FromFileInput(Path.Combine(tempFolderName, "%09d.png"), false)
+ .OutputToFile(output, true, options => options
+ .Resize(firstImage.Width, firstImage.Height)
+ .WithFramerate(frameRate))
+ .ProcessSynchronously();
+ }
+ finally
+ {
+ Cleanup(temporaryImageFiles);
+ Directory.Delete(tempFolderName);
+ }
+ }
+ ///
+ /// Adds a poster image to an audio file.
+ ///
+ /// Source image file.
+ /// Source audio file.
+ /// Output video file.
+ ///
+ public static bool PosterWithAudio(string image, string audio, string output)
+ {
+ FFMpegHelper.ExtensionExceptionCheck(output, FileExtension.Mp4);
+ using (var img = Image.FromFile(image))
+ FFMpegHelper.ConversionSizeExceptionCheck(img);
+
+ return FFMpegArguments
+ .FromFileInput(image, false, options => options
+ .Loop(1))
+ .AddFileInput(audio)
+ .OutputToFile(output, true, options => options
+ .WithVideoCodec(VideoCodec.LibX264)
+ .CopyChannel()
+ .WithConstantRateFactor(21)
+ .WithAudioBitrate(AudioQuality.Normal)
+ .UsingShortest())
+ .ProcessSynchronously();
+ }
+ ///
+ /// Saves a 'png' thumbnail to an in-memory bitmap
+ ///
+ /// Source video file.
+ /// Seek position where the thumbnail should be taken.
+ /// Thumbnail size. If width or height equal 0, the other will be computed automatically.
+ /// Selected video stream index.
+ /// Input file index
+ /// Bitmap with the requested snapshot.
+ public static Bitmap Snapshot(string input, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, int inputFileIndex = 0)
+ {
+ var source = FFProbe.Analyse(input);
+ var (arguments, outputOptions) = BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex);
+ using var ms = new MemoryStream();
+
+ arguments
+ .OutputToPipe(new StreamPipeSink(ms), options => outputOptions(options
+ .ForceFormat("rawvideo")))
+ .ProcessSynchronously();
+
+ ms.Position = 0;
+ using var bitmap = new Bitmap(ms);
+ return bitmap.Clone(new Rectangle(0, 0, bitmap.Width, bitmap.Height), bitmap.PixelFormat);
+ }
+ ///
+ /// Saves a 'png' thumbnail to an in-memory bitmap
+ ///
+ /// Source video file.
+ /// Seek position where the thumbnail should be taken.
+ /// Thumbnail size. If width or height equal 0, the other will be computed automatically.
+ /// Selected video stream index.
+ /// Input file index
+ /// Bitmap with the requested snapshot.
+ public static async Task SnapshotAsync(string input, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, int inputFileIndex = 0)
+ {
+ var source = await FFProbe.AnalyseAsync(input).ConfigureAwait(false);
+ var (arguments, outputOptions) = BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex);
+ using var ms = new MemoryStream();
+
+ await arguments
+ .OutputToPipe(new StreamPipeSink(ms), options => outputOptions(options
+ .ForceFormat("rawvideo")))
+ .ProcessAsynchronously();
+
+ ms.Position = 0;
+ return new Bitmap(ms);
+ }
+ private static void Cleanup(IEnumerable pathList)
+ {
+ foreach (var path in pathList)
+ {
+ if (File.Exists(path))
+ File.Delete(path);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/FFMpegCore/ImageInfo.cs b/FFMpegCore.Extensions.System.Drawing.Common/ImageInfo.cs
similarity index 100%
rename from FFMpegCore/ImageInfo.cs
rename to FFMpegCore.Extensions.System.Drawing.Common/ImageInfo.cs
diff --git a/FFMpegCore.Test/AudioTest.cs b/FFMpegCore.Test/AudioTest.cs
index 795fedf..23c4e79 100644
--- a/FFMpegCore.Test/AudioTest.cs
+++ b/FFMpegCore.Test/AudioTest.cs
@@ -9,6 +9,7 @@
using System.IO;
using System.Linq;
using System.Threading.Tasks;
+using FFMpegCore.Extensions.System.Drawing.Common;
namespace FFMpegCore.Test
{
@@ -68,7 +69,7 @@ public void Audio_Add()
public void Image_AddAudio()
{
using var outputFile = new TemporaryFile("out.mp4");
- FFMpeg.PosterWithAudio(TestResources.PngImage, TestResources.Mp3Audio, outputFile);
+ FFMpegImage.PosterWithAudio(TestResources.PngImage, TestResources.Mp3Audio, outputFile);
var analysis = FFProbe.Analyse(TestResources.Mp3Audio);
Assert.IsTrue(analysis.Duration.TotalSeconds > 0);
Assert.IsTrue(File.Exists(outputFile));
diff --git a/FFMpegCore.Test/FFMpegCore.Test.csproj b/FFMpegCore.Test/FFMpegCore.Test.csproj
index 4ac890c..c84f3fe 100644
--- a/FFMpegCore.Test/FFMpegCore.Test.csproj
+++ b/FFMpegCore.Test/FFMpegCore.Test.csproj
@@ -50,6 +50,7 @@
+
diff --git a/FFMpegCore.Test/Utilities/BitmapSources.cs b/FFMpegCore.Test/Utilities/BitmapSources.cs
index 8ea02e8..50dd691 100644
--- a/FFMpegCore.Test/Utilities/BitmapSources.cs
+++ b/FFMpegCore.Test/Utilities/BitmapSources.cs
@@ -1,9 +1,9 @@
-using FFMpegCore.Extend;
-using System;
+using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Numerics;
+using FFMpegCore.Extensions.System.Drawing.Common;
using FFMpegCore.Pipes;
namespace FFMpegCore.Test
diff --git a/FFMpegCore.Test/VideoTest.cs b/FFMpegCore.Test/VideoTest.cs
index 8f73575..ad72e57 100644
--- a/FFMpegCore.Test/VideoTest.cs
+++ b/FFMpegCore.Test/VideoTest.cs
@@ -13,6 +13,7 @@
using System.Text;
using System.Threading;
using System.Threading.Tasks;
+using FFMpegCore.Extensions.System.Drawing.Common;
namespace FFMpegCore.Test
{
@@ -402,8 +403,8 @@ public void Video_ToMP4_Resize_Args_Pipe(System.Drawing.Imaging.PixelFormat pixe
public void Video_Snapshot_InMemory()
{
var input = FFProbe.Analyse(TestResources.Mp4Video);
- using var bitmap = FFMpeg.Snapshot(TestResources.Mp4Video);
-
+ using var bitmap = FFMpegImage.Snapshot(TestResources.Mp4Video);
+
Assert.AreEqual(input.PrimaryVideoStream!.Width, bitmap.Width);
Assert.AreEqual(input.PrimaryVideoStream.Height, bitmap.Height);
Assert.AreEqual(bitmap.RawFormat, ImageFormat.Png);
@@ -460,8 +461,8 @@ public void Video_Join_Image_Sequence()
}
});
- using var outputFile = new TemporaryFile("out.mp4");
- var success = FFMpeg.JoinImageSequence(outputFile, images: imageSet.ToArray());
+ var outputFile = new TemporaryFile("out.mp4");
+ var success = FFMpegImage.JoinImageSequence(outputFile, images: imageSet.ToArray());
Assert.IsTrue(success);
var result = FFProbe.Analyse(outputFile);
Assert.AreEqual(3, result.Duration.Seconds);
diff --git a/FFMpegCore.sln b/FFMpegCore.sln
index 7a27980..5a9faa8 100644
--- a/FFMpegCore.sln
+++ b/FFMpegCore.sln
@@ -9,6 +9,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FFMpegCore.Test", "FFMpegCo
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FFMpegCore.Examples", "FFMpegCore.Examples\FFMpegCore.Examples.csproj", "{3125CF91-FFBD-4E4E-8930-247116AFE772}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FFMpegCore.Extensions.System.Drawing.Common", "FFMpegCore.Extensions.System.Drawing.Common\FFMpegCore.Extensions.System.Drawing.Common.csproj", "{9C1A4930-9369-4A18-AD98-929A2A510D80}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -27,6 +29,10 @@ Global
{3125CF91-FFBD-4E4E-8930-247116AFE772}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3125CF91-FFBD-4E4E-8930-247116AFE772}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3125CF91-FFBD-4E4E-8930-247116AFE772}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9C1A4930-9369-4A18-AD98-929A2A510D80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9C1A4930-9369-4A18-AD98-929A2A510D80}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9C1A4930-9369-4A18-AD98-929A2A510D80}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9C1A4930-9369-4A18-AD98-929A2A510D80}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/FFMpegCore/FFMpeg/FFMpeg.cs b/FFMpegCore/FFMpeg/FFMpeg.cs
index 9e9e0ce..165ec31 100644
--- a/FFMpegCore/FFMpeg/FFMpeg.cs
+++ b/FFMpegCore/FFMpeg/FFMpeg.cs
@@ -59,54 +59,6 @@ public static async Task SnapshotAsync(string input, string output, Size?
.ProcessAsynchronously();
}
- ///
- /// Saves a 'png' thumbnail to an in-memory bitmap
- ///
- /// Source video file.
- /// Seek position where the thumbnail should be taken.
- /// Thumbnail size. If width or height equal 0, the other will be computed automatically.
- /// Selected video stream index.
- /// Input file index
- /// Bitmap with the requested snapshot.
- public static Bitmap Snapshot(string input, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, int inputFileIndex = 0)
- {
- var source = FFProbe.Analyse(input);
- var (arguments, outputOptions) = BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex);
- using var ms = new MemoryStream();
-
- arguments
- .OutputToPipe(new StreamPipeSink(ms), options => outputOptions(options
- .ForceFormat("rawvideo")))
- .ProcessSynchronously();
-
- ms.Position = 0;
- using var bitmap = new Bitmap(ms);
- return bitmap.Clone(new Rectangle(0, 0, bitmap.Width, bitmap.Height), bitmap.PixelFormat);
- }
- ///
- /// Saves a 'png' thumbnail to an in-memory bitmap
- ///
- /// Source video file.
- /// Seek position where the thumbnail should be taken.
- /// Thumbnail size. If width or height equal 0, the other will be computed automatically.
- /// Selected video stream index.
- /// Input file index
- /// Bitmap with the requested snapshot.
- public static async Task SnapshotAsync(string input, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, int inputFileIndex = 0)
- {
- var source = await FFProbe.AnalyseAsync(input).ConfigureAwait(false);
- var (arguments, outputOptions) = BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex);
- using var ms = new MemoryStream();
-
- await arguments
- .OutputToPipe(new StreamPipeSink(ms), options => outputOptions(options
- .ForceFormat("rawvideo")))
- .ProcessAsynchronously();
-
- ms.Position = 0;
- return new Bitmap(ms);
- }
-
private static (FFMpegArguments, Action outputOptions) BuildSnapshotArguments(
string input,
IMediaAnalysis source,
diff --git a/FFMpegCore/FFMpegCore.csproj b/FFMpegCore/FFMpegCore.csproj
index 79d191e..a3251db 100644
--- a/FFMpegCore/FFMpegCore.csproj
+++ b/FFMpegCore/FFMpegCore.csproj
@@ -38,7 +38,6 @@
-
diff --git a/FFMpegCore/Helpers/FFMpegHelper.cs b/FFMpegCore/Helpers/FFMpegHelper.cs
index cb3b4cf..ecea946 100644
--- a/FFMpegCore/Helpers/FFMpegHelper.cs
+++ b/FFMpegCore/Helpers/FFMpegHelper.cs
@@ -10,13 +10,10 @@ public static class FFMpegHelper
{
private static bool _ffmpegVerified;
- public static void ConversionSizeExceptionCheck(Image image)
- => ConversionSizeExceptionCheck(image.Size.Width, image.Size.Height);
-
public static void ConversionSizeExceptionCheck(IMediaAnalysis info)
=> ConversionSizeExceptionCheck(info.PrimaryVideoStream!.Width, info.PrimaryVideoStream.Height);
- private static void ConversionSizeExceptionCheck(int width, int height)
+ public static void ConversionSizeExceptionCheck(int width, int height)
{
if (height % 2 != 0 || width % 2 != 0 )
throw new ArgumentException("FFMpeg yuv420p encoding requires the width and height to be a multiple of 2!");