diff --git a/OnnxStack.Core/Image/OnnxImage.cs b/OnnxStack.Core/Image/OnnxImage.cs
index 6cf0890..1716eeb 100644
--- a/OnnxStack.Core/Image/OnnxImage.cs
+++ b/OnnxStack.Core/Image/OnnxImage.cs
@@ -5,6 +5,7 @@
using SixLabors.ImageSharp.Processing;
using System;
using System.IO;
+using System.Threading;
using System.Threading.Tasks;
using ImageSharp = SixLabors.ImageSharp.Image;
@@ -209,9 +210,9 @@ public void CopyToStream(Stream destination)
///
/// The destination.
///
- public Task CopyToStreamAsync(Stream destination)
+ public Task CopyToStreamAsync(Stream destination, CancellationToken cancellationToken)
{
- return _imageData.SaveAsPngAsync(destination);
+ return _imageData.SaveAsPngAsync(destination, cancellationToken);
}
diff --git a/OnnxStack.Core/OnnxStack.Core.csproj b/OnnxStack.Core/OnnxStack.Core.csproj
index 17bfa5a..8e9d484 100644
--- a/OnnxStack.Core/OnnxStack.Core.csproj
+++ b/OnnxStack.Core/OnnxStack.Core.csproj
@@ -1,7 +1,7 @@
- 0.31.0
+ 0.31.10
net7.0
disable
disable
@@ -37,7 +37,6 @@
-
diff --git a/OnnxStack.Core/Video/OnnxVideo.cs b/OnnxStack.Core/Video/OnnxVideo.cs
index ee8f3e9..dc6f3f9 100644
--- a/OnnxStack.Core/Video/OnnxVideo.cs
+++ b/OnnxStack.Core/Video/OnnxVideo.cs
@@ -164,7 +164,7 @@ public void Dispose()
public static async Task FromFileAsync(string filename, float? frameRate = default, CancellationToken cancellationToken = default)
{
var videoBytes = await File.ReadAllBytesAsync(filename, cancellationToken);
- var videoInfo = await VideoHelper.ReadVideoInfoAsync(videoBytes);
+ var videoInfo = await VideoHelper.ReadVideoInfoAsync(videoBytes, cancellationToken);
if (frameRate.HasValue)
videoInfo = videoInfo with { FrameRate = Math.Min(videoInfo.FrameRate, frameRate.Value) };
diff --git a/OnnxStack.Core/Video/VideoHelper.cs b/OnnxStack.Core/Video/VideoHelper.cs
index b8c6721..abf8892 100644
--- a/OnnxStack.Core/Video/VideoHelper.cs
+++ b/OnnxStack.Core/Video/VideoHelper.cs
@@ -1,5 +1,4 @@
-using FFMpegCore;
-using OnnxStack.Core.Config;
+using OnnxStack.Core.Config;
using OnnxStack.Core.Image;
using System;
using System.Collections.Generic;
@@ -7,6 +6,7 @@
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
+using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
@@ -73,7 +73,7 @@ private static async Task WriteVideoFramesAsync(IEnumerable onnxImage
foreach (var image in onnxImages)
{
// Write each frame to the input stream of FFMPEG
- await videoWriter.StandardInput.BaseStream.WriteAsync(image.GetImageBytes(), cancellationToken);
+ await image.CopyToStreamAsync(videoWriter.StandardInput.BaseStream, cancellationToken);
}
// Done close stream and wait for app to process
@@ -103,7 +103,7 @@ public static async Task WriteVideoStreamAsync(VideoInfo videoInfo, IAsyncEnumer
await foreach (var frame in videoStream)
{
// Write each frame to the input stream of FFMPEG
- await frame.CopyToStreamAsync(videoWriter.StandardInput.BaseStream);
+ await frame.CopyToStreamAsync(videoWriter.StandardInput.BaseStream, cancellationToken);
}
// Done close stream and wait for app to process
@@ -118,12 +118,17 @@ public static async Task WriteVideoStreamAsync(VideoInfo videoInfo, IAsyncEnumer
///
/// The video bytes.
///
- public static async Task ReadVideoInfoAsync(byte[] videoBytes)
+ public static async Task ReadVideoInfoAsync(byte[] videoBytes, CancellationToken cancellationToken = default)
{
- using (var memoryStream = new MemoryStream(videoBytes))
+ string tempVideoPath = GetTempFilename();
+ try
+ {
+ await File.WriteAllBytesAsync(tempVideoPath, videoBytes, cancellationToken);
+ return await ReadVideoInfoAsync(tempVideoPath, cancellationToken);
+ }
+ finally
{
- var result = await FFProbe.AnalyseAsync(memoryStream).ConfigureAwait(false);
- return new VideoInfo(result.PrimaryVideoStream.Width, result.PrimaryVideoStream.Height, result.Duration, (int)result.PrimaryVideoStream.FrameRate);
+ DeleteTempFile(tempVideoPath);
}
}
@@ -133,10 +138,29 @@ public static async Task ReadVideoInfoAsync(byte[] videoBytes)
///
/// The filename.
///
- public static async Task ReadVideoInfoAsync(string filename)
+ public static async Task ReadVideoInfoAsync(string filename, CancellationToken cancellationToken = default)
{
- var result = await FFProbe.AnalyseAsync(filename).ConfigureAwait(false);
- return new VideoInfo(result.PrimaryVideoStream.Width, result.PrimaryVideoStream.Height, result.Duration, (int)result.PrimaryVideoStream.FrameRate);
+
+ using (var metadataReader = CreateMetadataReader(filename))
+ {
+ // Start FFMPEG
+ metadataReader.Start();
+
+ var videoInfo = default(VideoInfo);
+ using (StreamReader reader = metadataReader.StandardOutput)
+ {
+ string result = await reader.ReadToEndAsync();
+ var videoMetadata = JsonSerializer.Deserialize(result);
+ var videoStream = videoMetadata.Streams.FirstOrDefault();
+ if (videoStream is null)
+ throw new Exception("Failed to parse video stream metadata");
+
+ videoInfo = new VideoInfo(videoStream.Height, videoStream.Width, videoStream.Duration, videoStream.FramesPerSecond);
+ }
+
+ await metadataReader.WaitForExitAsync(cancellationToken);
+ return videoInfo;
+ }
}
@@ -308,7 +332,7 @@ private static Process CreateReader(string inputFile, float fps)
{
var ffmpegProcess = new Process();
ffmpegProcess.StartInfo.FileName = _configuration.FFmpegPath;
- ffmpegProcess.StartInfo.Arguments = $"-hide_banner -loglevel error -i \"{inputFile}\" -c:v png -r {fps} -f image2pipe -";
+ ffmpegProcess.StartInfo.Arguments = $"-hide_banner -loglevel error -hwaccel:v auto -i \"{inputFile}\" -c:v png -r {fps} -f image2pipe -";
ffmpegProcess.StartInfo.RedirectStandardOutput = true;
ffmpegProcess.StartInfo.UseShellExecute = false;
ffmpegProcess.StartInfo.CreateNoWindow = true;
@@ -329,7 +353,7 @@ private static Process CreateWriter(string outputFile, float fps, double aspectR
var codec = preserveTransparency ? "png" : "libx264";
var format = preserveTransparency ? "yuva420p" : "yuv420p";
ffmpegProcess.StartInfo.FileName = _configuration.FFmpegPath;
- ffmpegProcess.StartInfo.Arguments = $"-hide_banner -loglevel error -framerate {fps:F4} -i - -c:v {codec} -movflags +faststart -vf format={format} -aspect {aspectRatio} {outputFile}";
+ ffmpegProcess.StartInfo.Arguments = $"-hide_banner -loglevel error -framerate {fps:F4} -hwaccel:v auto -i - -c:v {codec} -movflags +faststart -vf format={format} -aspect {aspectRatio} {outputFile}";
ffmpegProcess.StartInfo.RedirectStandardInput = true;
ffmpegProcess.StartInfo.UseShellExecute = false;
ffmpegProcess.StartInfo.CreateNoWindow = true;
@@ -337,6 +361,23 @@ private static Process CreateWriter(string outputFile, float fps, double aspectR
}
+ ///
+ /// Creates the metadata reader.
+ ///
+ /// The input file.
+ ///
+ private static Process CreateMetadataReader(string inputFile)
+ {
+ var ffprobeProcess = new Process();
+ ffprobeProcess.StartInfo.FileName = _configuration.FFprobePath;
+ ffprobeProcess.StartInfo.Arguments = $"-v quiet -print_format json -show_format -show_streams {inputFile}";
+ ffprobeProcess.StartInfo.RedirectStandardOutput = true;
+ ffprobeProcess.StartInfo.UseShellExecute = false;
+ ffprobeProcess.StartInfo.CreateNoWindow = true;
+ return ffprobeProcess;
+ }
+
+
///
/// Determines whether we are at the start of a PNG image in the specified buffer.
///
diff --git a/OnnxStack.Core/Video/VideoInfo.cs b/OnnxStack.Core/Video/VideoInfo.cs
index 7b16855..fb6b626 100644
--- a/OnnxStack.Core/Video/VideoInfo.cs
+++ b/OnnxStack.Core/Video/VideoInfo.cs
@@ -1,4 +1,6 @@
using System;
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
namespace OnnxStack.Core.Video
{
@@ -12,6 +14,77 @@ public VideoInfo(int height, int width, TimeSpan duration, float frameRate) : th
public int Height { get; set; }
public int Width { get; set; }
- public double AspectRatio => (double)Height / Width;
+ public double AspectRatio => (double)Width / Height;
+ }
+
+ public record VideoMetadata
+ {
+ [JsonPropertyName("format")]
+ public VideoFormat Format { get; set; }
+
+ [JsonPropertyName("streams")]
+ public List Streams { get; set; }
+ }
+
+ [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
+ public record VideoFormat
+ {
+ [JsonPropertyName("filename")]
+ public string FileName { get; set; }
+
+ [JsonPropertyName("nb_streams")]
+ public int StreamCount { get; set; }
+
+ [JsonPropertyName("format_name")]
+ public string FormatName { get; set; }
+
+ [JsonPropertyName("format_long_name")]
+ public string FormatLongName { get; set; }
+
+ [JsonPropertyName("size")]
+ public long Size { get; set; }
+
+ [JsonPropertyName("bit_rate")]
+ public long BitRate { get; set; }
+ }
+
+ [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
+ public record VideoStream
+ {
+ [JsonPropertyName("codec_type")]
+ public string Type { get; set; }
+
+ [JsonPropertyName("codec_name")]
+ public string CodecName { get; set; }
+
+ [JsonPropertyName("codec_long_name")]
+ public string CodecLongName { get; set; }
+
+ [JsonPropertyName("pix_fmt")]
+ public string PixelFormat { get; set; }
+
+ [JsonPropertyName("width")]
+ public int Width { get; set; }
+
+ [JsonPropertyName("height")]
+ public int Height { get; set; }
+
+ [JsonPropertyName("nb_frames")]
+ public int FrameCount { get; set; }
+
+ [JsonPropertyName("duration")]
+ public float DurationSeconds { get; set; }
+
+ public float FramesPerSecond => GetFramesPerSecond();
+
+ public TimeSpan Duration => TimeSpan.FromSeconds(DurationSeconds);
+
+ private float GetFramesPerSecond()
+ {
+ if (FrameCount == 0 || DurationSeconds == 0)
+ return 0;
+
+ return FrameCount / DurationSeconds;
+ }
}
}