Skip to content

Commit 4be8d0e

Browse files
committed
Refactor video projects
1 parent 4b26030 commit 4be8d0e

File tree

12 files changed

+295
-352
lines changed

12 files changed

+295
-352
lines changed

TensorStack.Video.Windows/TensorStack.Video.Windows.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121

2222
<!--Common Packages-->
2323
<ItemGroup>
24+
<PackageReference Include="System.Linq.Async" Version="6.0.3" />
25+
<PackageReference Include="OpenCvSharp4" Version="4.11.0.20250507" />
2426
<PackageReference Include="OpenCvSharp4.runtime.win" Version="4.11.0.20250507" />
2527
</ItemGroup>
2628

TensorStack.Video/VideoInput.cs renamed to TensorStack.Video.Windows/VideoInput.cs

Lines changed: 36 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ namespace TensorStack.Video
1010
/// <summary>
1111
/// Class to handle processing of a video stream.
1212
/// </summary>
13-
public class VideoInput : VideoTensor
13+
public class VideoInput : VideoInputBase
1414
{
15-
private string _filename;
15+
private readonly string _sourceFile;
1616

1717
/// <summary>
1818
/// Initializes a new instance of the <see cref="VideoInput"/> class.
@@ -23,24 +23,46 @@ 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(VideoManager.LoadVideoTensor(filename, widthOverride, heightOverride, frameRateOverride, resizeMode))
27-
{
28-
_filename = filename;
29-
}
26+
: this(filename, VideoManager.LoadVideoTensor(filename, widthOverride, heightOverride, frameRateOverride, resizeMode)) { }
3027

3128
/// <summary>
3229
/// Initializes a new instance of the <see cref="VideoInput"/> class.
3330
/// </summary>
31+
/// <param name="filename">The filename.</param>
3432
/// <param name="videoTensor">The video tensor.</param>
35-
public VideoInput(VideoTensor videoTensor) : base(videoTensor, videoTensor.FrameRate) { }
33+
public VideoInput(string filename, VideoTensor videoTensor)
34+
: base(videoTensor)
35+
{
36+
_sourceFile = filename;
37+
}
38+
39+
40+
/// <summary>
41+
/// Gets the source video filename.
42+
/// </summary>
43+
public override string SourceFile => _sourceFile;
44+
3645

3746
/// <summary>
38-
/// Gets the filename.
47+
/// Save the Video to file
3948
/// </summary>
40-
/// <value>The filename.</value>
41-
public string Filename => _filename;
49+
/// <param name="filename">The filename.</param>
50+
public override void Save(string filename)
51+
{
52+
throw new System.NotImplementedException();
53+
}
4254

4355

56+
/// <summary>
57+
/// Save the Video to file
58+
/// </summary>
59+
/// <param name="filename">The filename.</param>
60+
/// <param name="cancellationToken">The cancellation token</param>
61+
public override Task SaveAsync(string filename, CancellationToken cancellationToken = default)
62+
{
63+
return SaveAsync(filename, frameRateOverride: default, cancellationToken: cancellationToken);
64+
}
65+
4466
/// <summary>
4567
/// Save the VideoTensor to file
4668
/// </summary>
@@ -60,7 +82,7 @@ public async Task SaveAsync(string filename, string videoCodec = "mp4v", float?
6082
/// <returns>VideoStream.</returns>
6183
public VideoInputStream CreateStream()
6284
{
63-
return new VideoInputStream(_filename);
85+
return new VideoInputStream(_sourceFile);
6486
}
6587

6688

@@ -70,17 +92,7 @@ public VideoInputStream CreateStream()
7092
/// <returns>Task&lt;VideoInputStream&gt;.</returns>
7193
public Task<VideoInputStream> CreateStreamAsync()
7294
{
73-
return VideoInputStream.CreateAsync(_filename);
74-
}
75-
76-
77-
/// <summary>
78-
/// Sets the filename.
79-
/// </summary>
80-
/// <param name="filename">The filename.</param>
81-
public void SetFilename(string filename)
82-
{
83-
_filename = filename;
95+
return VideoInputStream.CreateAsync(_sourceFile);
8496
}
8597

8698

@@ -95,7 +107,8 @@ public void SetFilename(string filename)
95107
/// <returns>A Task&lt;VideoInput&gt; representing the asynchronous operation.</returns>
96108
public static async Task<VideoInput> CreateAsync(string filename, int? widthOverride = default, int? heightOverride = default, float? frameRateOverride = default, ResizeMode resizeMode = ResizeMode.Crop, CancellationToken cancellationToken = default)
97109
{
98-
return new VideoInput(await VideoManager.LoadVideoTensorAsync(filename, widthOverride, heightOverride, frameRateOverride, resizeMode, cancellationToken));
110+
return new VideoInput(filename, await VideoManager.LoadVideoTensorAsync(filename, widthOverride, heightOverride, frameRateOverride, resizeMode, cancellationToken));
99111
}
112+
100113
}
101114
}

TensorStack.Video/VideoInputStream.cs renamed to TensorStack.Video.Windows/VideoInputStream.cs

Lines changed: 12 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -12,69 +12,23 @@
1212

1313
namespace TensorStack.Video
1414
{
15-
public class VideoInputStream
15+
public class VideoInputStream : VideoInputStreamBase
1616
{
17-
private readonly VideoInfo _videoInfo;
18-
19-
/// <summary>
20-
/// Initializes a new instance of the <see cref="VideoInputStream"/> class.
21-
/// </summary>
22-
/// <param name="videoInfo">The video information.</param>
23-
/// <param name="videoCodec">The video codec.</param>
24-
public VideoInputStream(VideoInfo videoInfo)
25-
{
26-
_videoInfo = videoInfo;
27-
}
28-
2917
/// <summary>
3018
/// Initializes a new instance of the <see cref="VideoInputStream"/> class.
3119
/// </summary>
3220
/// <param name="filename">The filename.</param>
3321
/// <param name="videoCodec">The video codec.</param>
3422
/// <exception cref="System.Exception">Failed to open video file.</exception>
35-
public VideoInputStream(string filename)
23+
public VideoInputStream(string filename)
3624
: this(VideoManager.LoadVideoInfo(filename)) { }
3725

3826
/// <summary>
39-
/// Gets the filename.
40-
/// </summary>
41-
/// <value>The filename.</value>
42-
public string Filename => _videoInfo.FileName;
43-
44-
/// <summary>
45-
/// Gets the video width.
46-
/// </summary>
47-
public int Width => _videoInfo.Width;
48-
49-
/// <summary>
50-
/// Gets the video height.
51-
/// </summary>
52-
public int Height => _videoInfo.Height;
53-
54-
/// <summary>
55-
/// Gets the video frame rate.
56-
/// </summary>
57-
public float FrameRate => _videoInfo.FrameRate;
58-
59-
/// <summary>
60-
/// Gets the video frame count.
61-
/// </summary>
62-
public int FrameCount => _videoInfo.FrameCount;
63-
64-
/// <summary>
65-
/// Gets the video codec.
66-
/// </summary>
67-
public string VideoCodec => _videoInfo.VideoCodec;
68-
69-
/// <summary>
70-
/// Gets the duration.
71-
/// </summary>
72-
public TimeSpan Duration => _videoInfo.Duration;
73-
74-
/// <summary>
75-
/// Gets the thumbnail.
27+
/// Initializes a new instance of the <see cref="VideoInputStream"/> class.
7628
/// </summary>
77-
public ImageTensor Thumbnail => _videoInfo.Thumbnail;
29+
/// <param name="videoInfo">The video information.</param>
30+
private VideoInputStream(VideoInfo videoInfo)
31+
: base(videoInfo) { }
7832

7933

8034
/// <summary>
@@ -87,7 +41,7 @@ public VideoInputStream(string filename)
8741
/// <returns>IAsyncEnumerable&lt;ImageFrame&gt;.</returns>
8842
public IAsyncEnumerable<VideoFrame> GetAsync(int? widthOverride = default, int? heightOverride = default, float? frameRateOverride = default, ResizeMode resizeMode = ResizeMode.Stretch, CancellationToken cancellationToken = default)
8943
{
90-
return VideoManager.ReadStreamAsync(_videoInfo.FileName, frameRateOverride, widthOverride, heightOverride, resizeMode, cancellationToken);
44+
return VideoManager.ReadStreamAsync(SourceFile, frameRateOverride, widthOverride, heightOverride, resizeMode, cancellationToken);
9145
}
9246

9347

@@ -103,7 +57,7 @@ public IAsyncEnumerable<VideoFrame> GetAsync(int? widthOverride = default, int?
10357
/// <returns>Task.</returns>
10458
public Task SaveAsync(IAsyncEnumerable<VideoFrame> stream, string filename, string videoCodec = "mp4v", int? widthOverride = null, int? heightOverride = null, float? frameRateOverride = null, CancellationToken cancellationToken = default)
10559
{
106-
return VideoManager.WriteVideoStreamAsync(_videoInfo.FileName, stream, videoCodec, widthOverride, heightOverride, frameRateOverride, cancellationToken);
60+
return VideoManager.WriteVideoStreamAsync(SourceFile, stream, videoCodec, widthOverride, heightOverride, frameRateOverride, cancellationToken);
10761
}
10862

10963

@@ -134,10 +88,10 @@ public async Task<VideoTensor> CreateTensorAsync(int? widthOverride = default, i
13488
/// <exception cref="System.Exception">Destination video already exists</exception>
13589
public async Task<VideoInputStream> MoveAsync(string newFilename, bool overwrite = true)
13690
{
137-
if (!File.Exists(Filename))
91+
if (!File.Exists(SourceFile))
13892
throw new Exception("Source video not found");
13993

140-
File.Move(Filename, newFilename, overwrite);
94+
File.Move(SourceFile, newFilename, overwrite);
14195
return await CreateAsync(newFilename);
14296
}
14397

@@ -152,10 +106,10 @@ public async Task<VideoInputStream> MoveAsync(string newFilename, bool overwrite
152106
/// <exception cref="System.Exception">Destination video already exists</exception>
153107
public async Task<VideoInputStream> CopyAsync(string newFilename, bool overwrite = true)
154108
{
155-
if (!File.Exists(Filename))
109+
if (!File.Exists(SourceFile))
156110
throw new Exception("Source video not found");
157111

158-
File.Copy(Filename, newFilename, overwrite);
112+
File.Copy(SourceFile, newFilename, overwrite);
159113
return await CreateAsync(newFilename);
160114
}
161115

TensorStack.Video/VideoManager.cs renamed to TensorStack.Video.Windows/VideoManager.cs

Lines changed: 130 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ public static Task<VideoInfo> LoadVideoInfoAsync(string filename, int thumbSize
8181
/// <param name="heightOverride">The height.</param>
8282
/// <param name="frameRateOverride">The frame rate.</param>
8383
/// <returns>VideoTensor.</returns>
84-
internal static VideoTensor LoadVideoTensor(string videoFile, int? widthOverride = default, int? heightOverride = default, float? frameRateOverride = default, ResizeMode resizeMode = ResizeMode.Crop)
84+
public static VideoTensor LoadVideoTensor(string videoFile, int? widthOverride = default, int? heightOverride = default, float? frameRateOverride = default, ResizeMode resizeMode = ResizeMode.Crop)
8585
{
8686
return ReadVideo(videoFile, widthOverride, heightOverride, frameRateOverride, resizeMode);
8787
}
@@ -96,7 +96,7 @@ internal static VideoTensor LoadVideoTensor(string videoFile, int? widthOverride
9696
/// <param name="height">The height.</param>
9797
/// <param name="cancellationToken">The cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
9898
/// <returns>Task&lt;VideoTensor&gt;.</returns>
99-
internal static Task<VideoTensor> LoadVideoTensorAsync(string videoFile, int? widthOverride = default, int? heightOverride = default, float? frameRateOverride = default, ResizeMode resizeMode = ResizeMode.Crop, CancellationToken cancellationToken = default)
99+
public static Task<VideoTensor> LoadVideoTensorAsync(string videoFile, int? widthOverride = default, int? heightOverride = default, float? frameRateOverride = default, ResizeMode resizeMode = ResizeMode.Crop, CancellationToken cancellationToken = default)
100100
{
101101
return Task.Run(() => ReadVideo(videoFile, widthOverride, heightOverride, frameRateOverride, resizeMode, cancellationToken));
102102
}
@@ -455,5 +455,133 @@ private static async Task WriteVideoFramesAsync(string videoOutputFile, IAsyncEn
455455
// Block
456456
await Task.WhenAll(readerTask, processTask, writerTask);
457457
}
458+
459+
460+
461+
/// <summary>
462+
/// Converts Matrix to Tensor.
463+
/// </summary>
464+
/// <param name="matrix">The matrix.</param>
465+
/// <returns>Tensor&lt;System.Single&gt;.</returns>
466+
internal static unsafe ImageTensor ToTensor(this Mat matrix, Size cropSize = default)
467+
{
468+
int cropX = 0;
469+
int cropY = 0;
470+
int height = matrix.Rows;
471+
int width = matrix.Cols;
472+
473+
if (cropSize != default)
474+
{
475+
if (width == cropSize.Width)
476+
{
477+
cropY = (height - cropSize.Height) / 2;
478+
height = cropSize.Height;
479+
}
480+
else if (height == cropSize.Height)
481+
{
482+
cropX = (width - cropSize.Width) / 2;
483+
width = cropSize.Width;
484+
}
485+
}
486+
487+
var imageTensor = new ImageTensor([1, 4, height, width]);
488+
var destination = imageTensor.Memory.Span;
489+
490+
unsafe
491+
{
492+
var source = matrix.DataPointer;
493+
int srcStride = matrix.Cols * 3;
494+
int dstStride = height * width;
495+
for (int y = 0; y < height; y++)
496+
{
497+
for (int x = 0; x < width; x++)
498+
{
499+
int srcIndex = ((y + cropY) * matrix.Cols + (x + cropX)) * 3;
500+
int dstIndex = y * width + x;
501+
502+
destination[0 * dstStride + dstIndex] = GetFloatValue(source[srcIndex + 2]); // R
503+
destination[1 * dstStride + dstIndex] = GetFloatValue(source[srcIndex + 1]); // G
504+
destination[2 * dstStride + dstIndex] = GetFloatValue(source[srcIndex + 0]); // B
505+
destination[3 * dstStride + dstIndex] = GetFloatValue(byte.MaxValue); // A
506+
}
507+
}
508+
}
509+
510+
return imageTensor;
511+
}
512+
513+
514+
/// <summary>
515+
/// Converts Tensor to OpenCv Matrix.
516+
/// </summary>
517+
/// <param name="tensor">The tensor.</param>
518+
/// <returns>Mat.</returns>
519+
internal static unsafe Mat ToMatrix(this Tensor<float> tensor)
520+
{
521+
var channels = tensor.Dimensions[1];
522+
var height = tensor.Dimensions[2];
523+
var width = tensor.Dimensions[3];
524+
525+
var matrix = new Mat(height, width, MatType.CV_8UC3);
526+
var source = tensor.Span;
527+
var destination = matrix.DataPointer;
528+
for (int y = 0; y < height; y++)
529+
{
530+
for (int x = 0; x < width; x++)
531+
{
532+
int offset = y * width + x;
533+
534+
if (channels == 1)
535+
{
536+
byte gray = GetByteValue(source[offset]);
537+
destination[offset * 3 + 0] = gray; // B
538+
destination[offset * 3 + 1] = gray; // G
539+
destination[offset * 3 + 2] = gray; // R
540+
}
541+
else
542+
{
543+
destination[offset * 3 + 0] = GetByteValue(source[2 * width * height + offset]); // B
544+
destination[offset * 3 + 1] = GetByteValue(source[1 * width * height + offset]); // G
545+
destination[offset * 3 + 2] = GetByteValue(source[0 * width * height + offset]); // R
546+
}
547+
}
548+
}
549+
550+
return matrix;
551+
}
552+
553+
554+
/// <summary>
555+
/// Gets the normalized byte value.
556+
/// </summary>
557+
/// <param name="value">The value.</param>
558+
internal static byte GetByteValue(this float value)
559+
{
560+
return (byte)Math.Clamp((value + 1.0f) * 0.5f * 255f, 0, 255);
561+
}
562+
563+
564+
/// <summary>
565+
/// Gets the normalized float value.
566+
/// </summary>
567+
/// <param name="value">The value.</param>
568+
internal static float GetFloatValue(this byte value)
569+
{
570+
return (value / 255f) * 2.0f - 1.0f;
571+
}
572+
573+
574+
/// <summary>
575+
/// Null if zero.
576+
/// </summary>
577+
/// <param name="value">The value.</param>
578+
/// <returns>System.Nullable&lt;System.Int32&gt;.</returns>
579+
internal static int? NullIfZero(this int? value)
580+
{
581+
if (value.HasValue && value.Value == 0)
582+
return null;
583+
584+
return value;
585+
}
458586
}
459587
}

0 commit comments

Comments
 (0)