Skip to content

Commit fde6e26

Browse files
committed
Support Video + Audio
1 parent a5d1b40 commit fde6e26

File tree

9 files changed

+198
-104
lines changed

9 files changed

+198
-104
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
using System;
2+
using System.IO;
3+
4+
namespace TensorStack.Common.Common
5+
{
6+
public static class FileHelper
7+
{
8+
public static bool DeleteFile(string filename)
9+
{
10+
try
11+
{
12+
if (File.Exists(filename))
13+
return false;
14+
15+
File.Delete(filename);
16+
return true;
17+
}
18+
catch (Exception)
19+
{
20+
return false;
21+
}
22+
}
23+
24+
25+
public static void DeleteFiles(params string[] filenames)
26+
{
27+
foreach (string filename in filenames)
28+
{
29+
DeleteFile(filename);
30+
}
31+
}
32+
33+
34+
public static string RandomFileName(string extension)
35+
{
36+
var ext = Path.HasExtension(extension) ? Path.GetExtension(extension) : extension;
37+
return $"{Path.GetFileNameWithoutExtension(Path.GetRandomFileName())}.{ext.Trim('.')}";
38+
}
39+
40+
41+
public static string RandomFileName(string directory, string extension)
42+
{
43+
return Path.Combine(directory, RandomFileName(extension));
44+
}
45+
}
46+
}

TensorStack.Video.Windows/Extensions.cs

Lines changed: 0 additions & 10 deletions
This file was deleted.
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
using System;
2+
using System.Diagnostics;
3+
using System.IO;
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
using TensorStack.Common.Common;
7+
using TensorStack.Common.Video;
8+
9+
namespace TensorStack.Video
10+
{
11+
public class VideoService : IVideoService
12+
{
13+
private readonly IVideoConfiguration _configuration;
14+
15+
/// <summary>
16+
/// Initializes a new instance of the <see cref="VideoService"/> class.
17+
/// </summary>
18+
/// <param name="configuration">The configuration.</param>
19+
public VideoService(IVideoConfiguration configuration)
20+
{
21+
_configuration = configuration;
22+
}
23+
24+
/// <summary>
25+
/// Get video information
26+
/// </summary>
27+
/// <param name="filename">The filename.</param>
28+
public async Task<VideoInfo> GetInfoAsync(string filename)
29+
{
30+
return await VideoManager.LoadVideoInfoAsync(filename);
31+
}
32+
33+
34+
/// <summary>
35+
/// Get the Video stream
36+
/// </summary>
37+
/// <param name="filename">The filename.</param>
38+
public async Task<VideoInputStream> GetStreamAsync(string filename)
39+
{
40+
return await VideoInputStream.CreateAsync(filename);
41+
}
42+
43+
44+
/// <summary>
45+
/// Saves the stream aync.
46+
/// </summary>
47+
/// <param name="videoInput">The video input.</param>
48+
/// <param name="videoFile">The video file.</param>
49+
/// <param name="frameProcessor">The frame processor.</param>
50+
/// <param name="cancellationToken">The cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
51+
public async Task<VideoInputStream> SaveStreamAync(VideoInputStream videoInput, string videoOutputFile, Func<VideoFrame, Task<VideoFrame>> frameProcessor, CancellationToken cancellationToken = default)
52+
{
53+
var videoFrames = videoInput.GetAsync(cancellationToken: cancellationToken);
54+
await VideoManager.WriteVideoStreamAsync(videoOutputFile, videoFrames, frameProcessor, _configuration.ReadBuffer, _configuration.ReadBuffer, _configuration.VideoCodec, cancellationToken: cancellationToken);
55+
await AddAudioAsync(videoOutputFile, videoInput.Filename, cancellationToken);
56+
return await VideoInputStream.CreateAsync(videoOutputFile);
57+
}
58+
59+
60+
61+
private async Task AddAudioAsync(string target, string source, CancellationToken cancellationToken = default)
62+
{
63+
var tempFile = FileHelper.RandomFileName(_configuration.DirectoryTemp, target);
64+
var arguments = $"-i \"{target}\" -i \"{source}\" -c:v copy -c:a copy -map 0:v:0 -map 1:a:0 -y \"{tempFile}\"";
65+
66+
try
67+
{
68+
using (var videoWriter = CreateProcess(arguments))
69+
{
70+
videoWriter.Start();
71+
await videoWriter.WaitForExitAsync(cancellationToken);
72+
}
73+
74+
if (File.Exists(tempFile))
75+
File.Move(tempFile, target, true);
76+
}
77+
finally
78+
{
79+
FileHelper.DeleteFile(tempFile);
80+
}
81+
}
82+
83+
84+
private Process CreateProcess(string arguments)
85+
{
86+
var process = new Process();
87+
process.StartInfo.FileName = _configuration.FFmpegPath;
88+
process.StartInfo.Arguments = arguments;
89+
process.StartInfo.RedirectStandardInput = true;
90+
process.StartInfo.UseShellExecute = false;
91+
process.StartInfo.CreateNoWindow = true;
92+
return process;
93+
}
94+
95+
}
96+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright (c) TensorStack. All rights reserved.
2+
// Licensed under the Apache 2.0 License.
3+
namespace TensorStack.Video
4+
{
5+
public interface IVideoConfiguration
6+
{
7+
string FFmpegPath { get; set; }
8+
string FFprobePath { get; set; }
9+
string DirectoryTemp { get; set; }
10+
11+
int ReadBuffer { get; set; }
12+
int WriteBuffer { get; set; }
13+
string VideoCodec { get; set; }
14+
}
15+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using System;
2+
using System.Threading;
3+
using System.Threading.Tasks;
4+
using TensorStack.Common.Video;
5+
6+
namespace TensorStack.Video
7+
{
8+
public interface IVideoService
9+
{
10+
Task<VideoInfo> GetInfoAsync(string filename);
11+
Task<VideoInputStream> GetStreamAsync(string filename);
12+
Task<VideoInputStream> SaveStreamAync(VideoInputStream videoInput, string videoOutputFile, Func<VideoFrame, Task<VideoFrame>> frameProcessor, CancellationToken cancellationToken = default);
13+
}
14+
}

TensorStack.Video/Extensions.cs

Lines changed: 8 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -2,82 +2,23 @@
22
// Licensed under the Apache 2.0 License.
33
using OpenCvSharp;
44
using System;
5-
using System.Collections.Generic;
6-
using System.Linq;
7-
using System.Runtime.CompilerServices;
8-
using System.Threading;
9-
using System.Threading.Tasks;
105
using TensorStack.Common.Tensor;
11-
using TensorStack.Common.Video;
126

137
namespace TensorStack.Video
148
{
159
public static class Extensions
1610
{
1711
/// <summary>
18-
/// Saves the video.
12+
/// Converts Matrix to Tensor.
1913
/// </summary>
20-
/// <param name="imageFrames">The image frames.</param>
21-
/// <param name="videoFile">The video file.</param>
22-
/// <param name="framerate">The framerate.</param>
23-
/// <param name="width">The width.</param>
24-
/// <param name="height">The height.</param>
25-
/// <param name="videoCodec">The video codec.</param>
26-
/// <param name="cancellationToken">The cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
27-
public static async Task SaveAync(this IAsyncEnumerable<ImageTensor> imageFrames, string videoFile, float framerate, string videoCodec = "mp4v", int? widthOverride = null, int? heightOverride = null, CancellationToken cancellationToken = default)
28-
{
29-
var videoFrames = imageFrames.AsVideoFrames(framerate, cancellationToken);
30-
await VideoService.WriteVideoStreamAsync(videoFile, videoFrames, videoCodec, widthOverride, heightOverride, framerate, cancellationToken);
31-
}
32-
33-
34-
/// <summary>
35-
/// Saves the video.
36-
/// </summary>
37-
/// <param name="imageFrames">The image frames.</param>
38-
/// <param name="videoFile">The video file.</param>
39-
/// <param name="framerate">The framerate.</param>
40-
/// <param name="videoCodec">The video codec.</param>
41-
/// <param name="cancellationToken">The cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
42-
public static async Task SaveAync(this IAsyncEnumerable<VideoFrame> videoFrames, string videoFile, string videoCodec = "mp4v", int? widthOverride = null, int? heightOverride = null, float? frameRateOverride = null, CancellationToken cancellationToken = default)
43-
{
44-
await VideoService.WriteVideoStreamAsync(videoFile, videoFrames, videoCodec, widthOverride, heightOverride, frameRateOverride, cancellationToken);
45-
}
46-
47-
48-
/// <summary>
49-
/// Saves the video frames processing each frame [Read -> Process -> Write].
50-
/// Reads an writes are buffered allowing higher processing thoughput
51-
/// </summary>
52-
/// <param name="videoInput">The VideoInputStream.</param>
53-
/// <param name="videoFile">The video file.</param>
54-
/// <param name="frameProcessor">The frame processor.</param>
55-
/// <param name="readBuffer">The read buffer (frames).</param>
56-
/// <param name="writeBuffer">The write buffer (frames).</param>
57-
/// <param name="widthOverride">The output width override.</param>
58-
/// <param name="heightOverride">The output height override.</param>
59-
/// <param name="frameRateOverride">The output frame rate override.</param>
60-
/// <param name="videoCodec">The video codec.</param>
61-
/// <param name="cancellationToken">The cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
62-
public static async Task<VideoInputStream> SaveAync(this VideoInputStream videoInput, string videoFile, Func<VideoFrame, Task<VideoFrame>> frameProcessor, int readBuffer = 16, int writeBuffer = 16, string videoCodec = "mp4v", int? widthOverride = null, int? heightOverride = null, float? frameRateOverride = null, CancellationToken cancellationToken = default)
63-
{
64-
var videoFrames = videoInput.GetAsync(cancellationToken: cancellationToken);
65-
await VideoService.WriteVideoStreamAsync(videoFile, videoFrames, frameProcessor, readBuffer, writeBuffer, videoCodec, widthOverride, heightOverride, frameRateOverride, cancellationToken);
66-
return await VideoInputStream.CreateAsync(videoFile);
67-
}
68-
69-
70-
/// <summary>
71-
/// Converts Mat to Tensor.
72-
/// </summary>
73-
/// <param name="mat">The mat.</param>
14+
/// <param name="matrix">The matrix.</param>
7415
/// <returns>Tensor&lt;System.Single&gt;.</returns>
75-
internal static unsafe ImageTensor ToTensor(this Mat mat, Size cropSize = default)
16+
internal static unsafe ImageTensor ToTensor(this Mat matrix, Size cropSize = default)
7617
{
7718
int cropX = 0;
7819
int cropY = 0;
79-
int height = mat.Rows;
80-
int width = mat.Cols;
20+
int height = matrix.Rows;
21+
int width = matrix.Cols;
8122

8223
if (cropSize != default)
8324
{
@@ -98,14 +39,14 @@ internal static unsafe ImageTensor ToTensor(this Mat mat, Size cropSize = defaul
9839

9940
unsafe
10041
{
101-
var source = mat.DataPointer;
102-
int srcStride = mat.Cols * 3;
42+
var source = matrix.DataPointer;
43+
int srcStride = matrix.Cols * 3;
10344
int dstStride = height * width;
10445
for (int y = 0; y < height; y++)
10546
{
10647
for (int x = 0; x < width; x++)
10748
{
108-
int srcIndex = ((y + cropY) * mat.Cols + (x + cropX)) * 3;
49+
int srcIndex = ((y + cropY) * matrix.Cols + (x + cropX)) * 3;
10950
int dstIndex = y * width + x;
11051

11152
destination[0 * dstStride + dstIndex] = GetFloatValue(source[srcIndex + 2]); // R
@@ -193,14 +134,5 @@ internal static float GetFloatValue(this byte value)
193134
return value;
194135
}
195136

196-
197-
internal static async IAsyncEnumerable<VideoFrame> AsVideoFrames(this IAsyncEnumerable<ImageTensor> videoFrames, float frameRate, [EnumeratorCancellation] CancellationToken cancellationToken = default)
198-
{
199-
var frameIndex = 0;
200-
await foreach (var videoFrame in videoFrames.WithCancellation(cancellationToken))
201-
{
202-
yield return new VideoFrame(frameIndex++, videoFrame, frameRate);
203-
}
204-
}
205137
}
206138
}

TensorStack.Video/VideoInput.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public class VideoInput : VideoTensor
2323
/// <param name="frameRate">The frame rate.</param>
2424
/// <param name="videoCodec">The video codec.</param>
2525
public VideoInput(string filename, int? widthOverride = default, int? heightOverride = default, float? frameRateOverride = default, ResizeMode resizeMode = ResizeMode.Crop)
26-
: this(VideoService.LoadVideoTensor(filename, widthOverride, heightOverride, frameRateOverride, resizeMode))
26+
: this(VideoManager.LoadVideoTensor(filename, widthOverride, heightOverride, frameRateOverride, resizeMode))
2727
{
2828
_filename = filename;
2929
}
@@ -50,7 +50,7 @@ public VideoInput(VideoTensor videoTensor) : base(videoTensor, videoTensor.Frame
5050
/// <returns>A Task representing the asynchronous operation.</returns>
5151
public async Task SaveAsync(string filename, string videoCodec = "mp4v", float? frameRateOverride = default, CancellationToken cancellationToken = default)
5252
{
53-
await VideoService.SaveVideoTensorAync(filename, this, videoCodec, frameRateOverride, cancellationToken);
53+
await VideoManager.SaveVideoTensorAync(filename, this, videoCodec, frameRateOverride, cancellationToken);
5454
}
5555

5656

@@ -95,7 +95,7 @@ public void SetFilename(string filename)
9595
/// <returns>A Task&lt;VideoInput&gt; representing the asynchronous operation.</returns>
9696
public static async Task<VideoInput> CreateAsync(string filename, int? widthOverride = default, int? heightOverride = default, float? frameRateOverride = default, ResizeMode resizeMode = ResizeMode.Crop, CancellationToken cancellationToken = default)
9797
{
98-
return new VideoInput(await VideoService.LoadVideoTensorAsync(filename, widthOverride, heightOverride, frameRateOverride, resizeMode, cancellationToken));
98+
return new VideoInput(await VideoManager.LoadVideoTensorAsync(filename, widthOverride, heightOverride, frameRateOverride, resizeMode, cancellationToken));
9999
}
100100
}
101101
}

TensorStack.Video/VideoInputStream.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ public VideoInputStream(VideoInfo videoInfo)
3232
/// <param name="filename">The filename.</param>
3333
/// <param name="videoCodec">The video codec.</param>
3434
/// <exception cref="System.Exception">Failed to open video file.</exception>
35-
public VideoInputStream(string filename) : this(VideoService.LoadVideoInfo(filename)) { }
35+
public VideoInputStream(string filename)
36+
: this(VideoManager.LoadVideoInfo(filename)) { }
3637

3738
/// <summary>
3839
/// Gets the filename.
@@ -86,7 +87,7 @@ public VideoInputStream(string filename) : this(VideoService.LoadVideoInfo(filen
8687
/// <returns>IAsyncEnumerable&lt;ImageFrame&gt;.</returns>
8788
public IAsyncEnumerable<VideoFrame> GetAsync(int? widthOverride = default, int? heightOverride = default, float? frameRateOverride = default, ResizeMode resizeMode = ResizeMode.Stretch, CancellationToken cancellationToken = default)
8889
{
89-
return VideoService.ReadStreamAsync(_videoInfo.FileName, frameRateOverride, widthOverride, heightOverride, resizeMode, cancellationToken);
90+
return VideoManager.ReadStreamAsync(_videoInfo.FileName, frameRateOverride, widthOverride, heightOverride, resizeMode, cancellationToken);
9091
}
9192

9293

@@ -102,7 +103,7 @@ public IAsyncEnumerable<VideoFrame> GetAsync(int? widthOverride = default, int?
102103
/// <returns>Task.</returns>
103104
public Task SaveAsync(IAsyncEnumerable<VideoFrame> stream, string filename, string videoCodec = "mp4v", int? widthOverride = null, int? heightOverride = null, float? frameRateOverride = null, CancellationToken cancellationToken = default)
104105
{
105-
return VideoService.WriteVideoStreamAsync(_videoInfo.FileName, stream, videoCodec, widthOverride, heightOverride, frameRateOverride, cancellationToken);
106+
return VideoManager.WriteVideoStreamAsync(_videoInfo.FileName, stream, videoCodec, widthOverride, heightOverride, frameRateOverride, cancellationToken);
106107
}
107108

108109

@@ -167,7 +168,7 @@ public async Task<VideoInputStream> CopyAsync(string newFilename, bool overwrite
167168
/// <returns>A Task&lt;VideoInputStream&gt; representing the asynchronous operation.</returns>
168169
public static async Task<VideoInputStream> CreateAsync(string filename)
169170
{
170-
var videoInfo = await VideoService.LoadVideoInfoAsync(filename);
171+
var videoInfo = await VideoManager.LoadVideoInfoAsync(filename);
171172
return new VideoInputStream(videoInfo);
172173
}
173174
}

0 commit comments

Comments
 (0)