diff --git a/FFMpegCore.Extensions.SkiaSharp/BitmapExtensions.cs b/FFMpegCore.Extensions.SkiaSharp/BitmapExtensions.cs
new file mode 100644
index 0000000..34e303a
--- /dev/null
+++ b/FFMpegCore.Extensions.SkiaSharp/BitmapExtensions.cs
@@ -0,0 +1,28 @@
+using SkiaSharp;
+
+namespace FFMpegCore.Extensions.SkiaSharp
+{
+ public static class BitmapExtensions
+ {
+ public static bool AddAudio(this SKBitmap poster, string audio, string output)
+ {
+ var destination = $"{Environment.TickCount}.png";
+ using (var fileStream = File.OpenWrite(destination))
+ {
+ poster.Encode(fileStream, SKEncodedImageFormat.Png, default); // PNG does not respect the quality parameter
+ }
+
+ try
+ {
+ return FFMpeg.PosterWithAudio(destination, audio, output);
+ }
+ finally
+ {
+ if (File.Exists(destination))
+ {
+ File.Delete(destination);
+ }
+ }
+ }
+ }
+}
diff --git a/FFMpegCore.Extensions.SkiaSharp/BitmapVideoFrameWrapper.cs b/FFMpegCore.Extensions.SkiaSharp/BitmapVideoFrameWrapper.cs
new file mode 100644
index 0000000..2556883
--- /dev/null
+++ b/FFMpegCore.Extensions.SkiaSharp/BitmapVideoFrameWrapper.cs
@@ -0,0 +1,58 @@
+using FFMpegCore.Pipes;
+using SkiaSharp;
+
+namespace FFMpegCore.Extensions.SkiaSharp
+{
+ public class BitmapVideoFrameWrapper : IVideoFrame, IDisposable
+ {
+ public int Width => Source.Width;
+
+ public int Height => Source.Height;
+
+ public string Format { get; private set; }
+
+ public SKBitmap Source { get; private set; }
+
+ public BitmapVideoFrameWrapper(SKBitmap bitmap)
+ {
+ Source = bitmap ?? throw new ArgumentNullException(nameof(bitmap));
+ Format = ConvertStreamFormat(bitmap.ColorType);
+ }
+
+ public void Serialize(Stream stream)
+ {
+ var data = Source.Bytes;
+ stream.Write(data, 0, data.Length);
+ }
+
+ public async Task SerializeAsync(Stream stream, CancellationToken token)
+ {
+ var data = Source.Bytes;
+ await stream.WriteAsync(data, 0, data.Length, token).ConfigureAwait(false);
+ }
+
+ public void Dispose()
+ {
+ Source.Dispose();
+ }
+
+ private static string ConvertStreamFormat(SKColorType fmt)
+ {
+ switch (fmt)
+ {
+ case SKColorType.Gray8:
+ return "gray8";
+ case SKColorType.Bgra8888:
+ return "bgra";
+ case SKColorType.Rgb888x:
+ return "rgb";
+ case SKColorType.Rgba8888:
+ return "rgba";
+ case SKColorType.Rgb565:
+ return "rgb565";
+ default:
+ throw new NotSupportedException($"Not supported pixel format {fmt}");
+ }
+ }
+ }
+}
diff --git a/FFMpegCore.Extensions.SkiaSharp/FFMpegCore.Extensions.SkiaSharp.csproj b/FFMpegCore.Extensions.SkiaSharp/FFMpegCore.Extensions.SkiaSharp.csproj
new file mode 100644
index 0000000..25e820a
--- /dev/null
+++ b/FFMpegCore.Extensions.SkiaSharp/FFMpegCore.Extensions.SkiaSharp.csproj
@@ -0,0 +1,22 @@
+
+
+
+ true
+ Image extension for FFMpegCore using System.Common.Drawing
+ 5.0.0
+
+
+ ffmpeg ffprobe convert video audio mediafile resize analyze muxing
+ Malte Rosenbjerg, Vlad Jerca, Max Bagryantsev
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/FFMpegCore.Extensions.SkiaSharp/FFMpegImage.cs b/FFMpegCore.Extensions.SkiaSharp/FFMpegImage.cs
new file mode 100644
index 0000000..69929d3
--- /dev/null
+++ b/FFMpegCore.Extensions.SkiaSharp/FFMpegImage.cs
@@ -0,0 +1,57 @@
+using System.Drawing;
+using FFMpegCore.Pipes;
+using SkiaSharp;
+
+namespace FFMpegCore.Extensions.SkiaSharp
+{
+ public static class FFMpegImage
+ {
+ ///
+ /// 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 SKBitmap Snapshot(string input, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, int inputFileIndex = 0)
+ {
+ var source = FFProbe.Analyse(input);
+ var (arguments, outputOptions) = SnapshotArgumentBuilder.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 = SKBitmap.Decode(ms);
+ return bitmap.Copy();
+ }
+ ///
+ /// 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) = SnapshotArgumentBuilder.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 SKBitmap.Decode(ms);
+ }
+ }
+}
diff --git a/FFMpegCore.Extensions.System.Drawing.Common/BitmapExtensions.cs b/FFMpegCore.Extensions.System.Drawing.Common/BitmapExtensions.cs
index b8a0c83..14cecaa 100644
--- a/FFMpegCore.Extensions.System.Drawing.Common/BitmapExtensions.cs
+++ b/FFMpegCore.Extensions.System.Drawing.Common/BitmapExtensions.cs
@@ -1,17 +1,13 @@
-using SkiaSharp;
+using System.Drawing;
namespace FFMpegCore.Extensions.System.Drawing.Common
{
public static class BitmapExtensions
{
- public static bool AddAudio(this SKBitmap poster, string audio, string output)
+ public static bool AddAudio(this Image poster, string audio, string output)
{
var destination = $"{Environment.TickCount}.png";
- using (var fileStream = File.OpenWrite(destination))
- {
- poster.Encode(fileStream, SKEncodedImageFormat.Png, default); // PNG does not respect the quality parameter
- }
-
+ poster.Save(destination);
try
{
return FFMpeg.PosterWithAudio(destination, audio, output);
diff --git a/FFMpegCore.Extensions.System.Drawing.Common/BitmapVideoFrameWrapper.cs b/FFMpegCore.Extensions.System.Drawing.Common/BitmapVideoFrameWrapper.cs
index 0439721..5462ca2 100644
--- a/FFMpegCore.Extensions.System.Drawing.Common/BitmapVideoFrameWrapper.cs
+++ b/FFMpegCore.Extensions.System.Drawing.Common/BitmapVideoFrameWrapper.cs
@@ -1,5 +1,7 @@
-using FFMpegCore.Pipes;
-using SkiaSharp;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.Runtime.InteropServices;
+using FFMpegCore.Pipes;
namespace FFMpegCore.Extensions.System.Drawing.Common
{
@@ -11,24 +13,44 @@ public class BitmapVideoFrameWrapper : IVideoFrame, IDisposable
public string Format { get; private set; }
- public SKBitmap Source { get; private set; }
+ public Bitmap Source { get; private set; }
- public BitmapVideoFrameWrapper(SKBitmap bitmap)
+ public BitmapVideoFrameWrapper(Bitmap bitmap)
{
Source = bitmap ?? throw new ArgumentNullException(nameof(bitmap));
- Format = ConvertStreamFormat(bitmap.ColorType);
+ Format = ConvertStreamFormat(bitmap.PixelFormat);
}
public void Serialize(Stream stream)
{
- var data = Source.Bytes;
- stream.Write(data, 0, data.Length);
+ var data = Source.LockBits(new Rectangle(0, 0, Width, Height), ImageLockMode.ReadOnly, Source.PixelFormat);
+
+ try
+ {
+ var buffer = new byte[data.Stride * data.Height];
+ Marshal.Copy(data.Scan0, buffer, 0, buffer.Length);
+ stream.Write(buffer, 0, buffer.Length);
+ }
+ finally
+ {
+ Source.UnlockBits(data);
+ }
}
public async Task SerializeAsync(Stream stream, CancellationToken token)
{
- var data = Source.Bytes;
- await stream.WriteAsync(data, 0, data.Length, token).ConfigureAwait(false);
+ var data = Source.LockBits(new Rectangle(0, 0, Width, Height), ImageLockMode.ReadOnly, Source.PixelFormat);
+
+ try
+ {
+ var buffer = new byte[data.Stride * data.Height];
+ Marshal.Copy(data.Scan0, buffer, 0, buffer.Length);
+ await stream.WriteAsync(buffer, 0, buffer.Length, token).ConfigureAwait(false);
+ }
+ finally
+ {
+ Source.UnlockBits(data);
+ }
}
public void Dispose()
@@ -36,20 +58,27 @@ public void Dispose()
Source.Dispose();
}
- private static string ConvertStreamFormat(SKColorType fmt)
+ private static string ConvertStreamFormat(PixelFormat fmt)
{
switch (fmt)
{
- case SKColorType.Gray8:
- return "gray8";
- case SKColorType.Bgra8888:
+ case PixelFormat.Format16bppGrayScale:
+ return "gray16le";
+ case PixelFormat.Format16bppRgb555:
+ return "bgr555le";
+ case PixelFormat.Format16bppRgb565:
+ return "bgr565le";
+ case PixelFormat.Format24bppRgb:
+ return "bgr24";
+ case PixelFormat.Format32bppArgb:
return "bgra";
- case SKColorType.Rgb888x:
- return "rgb";
- case SKColorType.Rgba8888:
+ case PixelFormat.Format32bppPArgb:
+ //This is not really same as argb32
+ return "argb";
+ case PixelFormat.Format32bppRgb:
return "rgba";
- case SKColorType.Rgb565:
- return "rgb565";
+ case PixelFormat.Format48bppRgb:
+ return "rgb48le";
default:
throw new NotSupportedException($"Not supported pixel format {fmt}");
}
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
index 25e820a..aafb577 100644
--- a/FFMpegCore.Extensions.System.Drawing.Common/FFMpegCore.Extensions.System.Drawing.Common.csproj
+++ b/FFMpegCore.Extensions.System.Drawing.Common/FFMpegCore.Extensions.System.Drawing.Common.csproj
@@ -11,8 +11,7 @@
-
-
+
diff --git a/FFMpegCore.Extensions.System.Drawing.Common/FFMpegImage.cs b/FFMpegCore.Extensions.System.Drawing.Common/FFMpegImage.cs
index 8cd0f26..f36f83d 100644
--- a/FFMpegCore.Extensions.System.Drawing.Common/FFMpegImage.cs
+++ b/FFMpegCore.Extensions.System.Drawing.Common/FFMpegImage.cs
@@ -1,6 +1,5 @@
using System.Drawing;
using FFMpegCore.Pipes;
-using SkiaSharp;
namespace FFMpegCore.Extensions.System.Drawing.Common
{
@@ -15,7 +14,7 @@ public static class FFMpegImage
/// Selected video stream index.
/// Input file index
/// Bitmap with the requested snapshot.
- public static SKBitmap Snapshot(string input, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, int inputFileIndex = 0)
+ 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) = SnapshotArgumentBuilder.BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex);
@@ -27,8 +26,8 @@ public static SKBitmap Snapshot(string input, Size? size = null, TimeSpan? captu
.ProcessSynchronously();
ms.Position = 0;
- using var bitmap = SKBitmap.Decode(ms);
- return bitmap.Copy();
+ 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
@@ -39,7 +38,7 @@ public static SKBitmap Snapshot(string input, Size? size = null, TimeSpan? captu
/// 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)
+ 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) = SnapshotArgumentBuilder.BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex);
@@ -51,7 +50,7 @@ await arguments
.ProcessAsynchronously();
ms.Position = 0;
- return SKBitmap.Decode(ms);
+ return new Bitmap(ms);
}
}
}
diff --git a/FFMpegCore.sln b/FFMpegCore.sln
index 5a9faa8..7ab0929 100644
--- a/FFMpegCore.sln
+++ b/FFMpegCore.sln
@@ -9,7 +9,9 @@ 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}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FFMpegCore.Extensions.System.Drawing.Common", "FFMpegCore.Extensions.System.Drawing.Common\FFMpegCore.Extensions.System.Drawing.Common.csproj", "{9C1A4930-9369-4A18-AD98-929A2A510D80}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FFMpegCore.Extensions.SkiaSharp", "FFMpegCore.Extensions.SkiaSharp\FFMpegCore.Extensions.SkiaSharp.csproj", "{5A76F9B7-3681-4551-A9B6-8D3AC5DA1090}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -33,6 +35,10 @@ Global
{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
+ {5A76F9B7-3681-4551-A9B6-8D3AC5DA1090}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5A76F9B7-3681-4551-A9B6-8D3AC5DA1090}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5A76F9B7-3681-4551-A9B6-8D3AC5DA1090}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5A76F9B7-3681-4551-A9B6-8D3AC5DA1090}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE