Skip to content
This repository has been archived by the owner on Feb 22, 2024. It is now read-only.

On-demand images, take two #165

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 103 additions & 45 deletions src/MMALSharp.Processing/Handlers/FileStreamCaptureHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,47 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;

namespace MMALSharp.Handlers
{
/// <summary>
/// Processes image data to a <see cref="FileStream"/>.
/// </summary>
public class FileStreamCaptureHandler : StreamCaptureHandler<FileStream>, IFileStreamCaptureHandler
public class FileStreamCaptureHandler : MemoryStreamCaptureHandler, IFileStreamCaptureHandler
{
private readonly bool _customFilename;
private int _increment;
private bool _skippingFirstPartialFrame = true;
private bool _continuousCapture;

/// <summary>
/// When true, the next full frame will be written. If <see cref="ContinuousCapture"/> is not also
/// true, this property will be reset to false after writing the image so that only one image is written.
/// </summary>
public bool CaptureNextFrame { get; set; }

/// <summary>
/// When true, every frame is written to storage.
/// </summary>
public bool ContinuousCapture
{
get => _continuousCapture;

set
{
_continuousCapture = value;
if(_continuousCapture)
{
CaptureNextFrame = true;
}
}
}

/// <summary>
/// Defines the image files' DateTime format string that is applied when the object is constructed with directory and extension arguments.
/// </summary>
public string FilenameDateTimeFormat { get; set; } = "dd-MMM-yy HH-mm-ss";

/// <summary>
/// A list of files that have been processed by this capture handler.
Expand All @@ -36,10 +67,15 @@ public class FileStreamCaptureHandler : StreamCaptureHandler<FileStream>, IFileS
public string Extension { get; set; }

/// <summary>
/// The name of the current file associated with the FileStream.
/// The filename to write next (if applicable).
/// </summary>
public string CurrentFilename { get; set; }

/// <summary>
/// The full pathname of the most recently written image file (if any).
/// </summary>
public string LastWrittenPathname { get; set; }

/// <summary>
/// Creates a new instance of the <see cref="FileStreamCaptureHandler"/> class without provisions for writing to a file. Supports
/// subclasses in which file output is optional.
Expand All @@ -51,82 +87,73 @@ public FileStreamCaptureHandler()

/// <summary>
/// Creates a new instance of the <see cref="FileStreamCaptureHandler"/> class with the specified directory and filename extension. Filenames will be in the
/// format "dd-MMM-yy HH-mm-ss" taken from this moment in time.
/// format defined by the <see cref="FilenameDateTimeFormat"/> property.
/// </summary>
/// <param name="directory">The directory to save captured data.</param>
/// <param name="extension">The filename extension for saving files.</param>
public FileStreamCaptureHandler(string directory, string extension)
/// <param name="continuousCapture">When true, every frame is written to a file.</param>
public FileStreamCaptureHandler(string directory, string extension, bool continuousCapture = true)
{
this.Directory = directory.TrimEnd('/');
this.Extension = extension.TrimStart('.');
this.ContinuousCapture = continuousCapture;

MMALLog.Logger.LogDebug($"{nameof(FileStreamCaptureHandler)} created for directory {this.Directory} and extension {this.Extension}");

System.IO.Directory.CreateDirectory(this.Directory);

var now = DateTime.Now.ToString("dd-MMM-yy HH-mm-ss");

int i = 1;

var fileName = $"{this.Directory}/{now}.{this.Extension}";

while (File.Exists(fileName))
{
fileName = $"{this.Directory}/{now} {i}.{this.Extension}";
i++;
}

var fileInfo = new FileInfo(fileName);

this.CurrentFilename = Path.GetFileNameWithoutExtension(fileInfo.Name);
this.CurrentStream = File.Create(fileName);
this.LastWrittenPathname = string.Empty;
this.CurrentStream = new MemoryStream();
}

/// <summary>
/// Creates a new instance of the <see cref="FileStreamCaptureHandler"/> class with the specified file path.
/// Creates a new instance of the <see cref="FileStreamCaptureHandler"/> class with the specified file pathname. An auto-incrementing number is added to each
/// new filename.
/// </summary>
/// <param name="fullPath">The absolute full path to save captured data to.</param>
public FileStreamCaptureHandler(string fullPath)
/// <param name="continuousCapture">When true, every frame is written to a file.</param>
public FileStreamCaptureHandler(string fullPath, bool continuousCapture = true)
{
var fileInfo = new FileInfo(fullPath);

this.Directory = fileInfo.DirectoryName;
this.CurrentFilename = Path.GetFileNameWithoutExtension(fileInfo.Name);

var ext = fullPath.Split('.').LastOrDefault();

if (string.IsNullOrEmpty(ext))
{
throw new ArgumentNullException(nameof(ext), "Could not get file extension from path string.");
}

this.Extension = ext;

MMALLog.Logger.LogDebug($"{nameof(FileStreamCaptureHandler)} created for directory {this.Directory} and extension {this.Extension}");
this.ContinuousCapture = continuousCapture;
this.Extension = ext;
var fileInfo = new FileInfo(fullPath);
this.Directory = fileInfo.DirectoryName;

this.CurrentFilename = Path.GetFileNameWithoutExtension(fileInfo.Name);
_customFilename = true;

MMALLog.Logger.LogDebug($"{nameof(FileStreamCaptureHandler)} created for pathname {fullPath}");

System.IO.Directory.CreateDirectory(this.Directory);

this.CurrentStream = File.Create(fullPath);
this.LastWrittenPathname = string.Empty;
this.CurrentStream = new MemoryStream();
}

/// <summary>
/// Gets the filename that a FileStream points to.
/// Gets the filename of the most recently stored image file.
/// </summary>
/// <returns>The filename.</returns>
public string GetFilename() =>
(this.CurrentStream != null) ? Path.GetFileNameWithoutExtension(this.CurrentStream.Name) : string.Empty;
public string GetFilename() =>
(!string.IsNullOrEmpty(this.LastWrittenPathname)) ? Path.GetFileNameWithoutExtension(this.LastWrittenPathname) : string.Empty;

/// <summary>
/// Gets the filepath that a FileStream points to.
/// Gets the pathname of the most recently stored image file.
/// </summary>
/// <returns>The filepath.</returns>
public string GetFilepath() =>
this.CurrentStream?.Name ?? string.Empty;
public string GetFilepath() =>
this.LastWrittenPathname;

/// <summary>
/// Creates a new File (FileStream), assigns it to the Stream instance of this class and disposes of any existing stream.
/// Outputs the current frame to a file. If a full frame hasn't been captured, a flag is set to capture the frame
/// once the end of stream is indicated.
/// </summary>
public virtual void NewFile()
{
Expand All @@ -135,20 +162,24 @@ public virtual void NewFile()
return;
}

this.CurrentStream?.Dispose();
// Wait for EOS
if(!CaptureNextFrame)
{
CaptureNextFrame = true;
return;
}

string newFilename = string.Empty;

if (_customFilename)
string newFilename;
if (!string.IsNullOrEmpty(CurrentFilename))
{
// If we're taking photos from video port, we don't want to be hammering File.Exists as this is added I/O overhead. Camera can take multiple photos per second
// so we can't do this when filename uses the current DateTime.
_increment++;
newFilename = $"{this.Directory}/{this.CurrentFilename} {_increment}.{this.Extension}";
newFilename = $"{this.Directory}/{_customFilename} {_increment}.{this.Extension}";
}
else
{
string tempFilename = DateTime.Now.ToString("dd-MMM-yy HH-mm-ss");
string tempFilename = DateTime.Now.ToString(FilenameDateTimeFormat);
int i = 1;

newFilename = $"{this.Directory}/{tempFilename}.{this.Extension}";
Expand All @@ -160,7 +191,34 @@ public virtual void NewFile()
}
}

this.CurrentStream = File.Create(newFilename);
using (FileStream fs = new FileStream(newFilename, FileMode.Create, FileAccess.Write))
{
this.CurrentStream.WriteTo(fs);
}

this.LastWrittenPathname = newFilename;
}

/// <summary>
/// If capture is active, output a new file.
/// </summary>
public virtual void NewFrame()
{
if (_skippingFirstPartialFrame)
{
_skippingFirstPartialFrame = false;
return;
}

if(_continuousCapture || this.CaptureNextFrame)
this.NewFile();

this.CaptureNextFrame = _continuousCapture;

if (this.CurrentStream != null)
{
this.CurrentStream.SetLength(0);
}
}

/// <inheritdoc />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ public interface IFileStreamCaptureHandler : IOutputCaptureHandler
/// </summary>
void NewFile();

/// <summary>
/// The callback handler has received an end-of-stream marker.
/// </summary>
void NewFrame();

/// <summary>
/// Gets the filepath that a FileStream points to.
/// </summary>
Expand Down
16 changes: 10 additions & 6 deletions src/MMALSharp.Processing/Handlers/ImageStreamCaptureHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,22 @@ namespace MMALSharp.Handlers
public class ImageStreamCaptureHandler : FileStreamCaptureHandler
{
/// <summary>
/// Creates a new instance of the <see cref="ImageStreamCaptureHandler"/> class with the specified directory and filename extension.
/// Creates a new instance of the <see cref="ImageStreamCaptureHandler"/> class with the specified directory and filename extension. Filenames will be in the
/// format defined by the <see cref="FilenameDateTimeFormat"/> property.
/// </summary>
/// <param name="directory">The directory to save captured images.</param>
/// <param name="extension">The filename extension for saving files.</param>
public ImageStreamCaptureHandler(string directory, string extension)
: base(directory, extension) { }
/// <param name="continuousCapture">When true, every frame is written to a file.</param>
public ImageStreamCaptureHandler(string directory, string extension, bool continuousCapture = true)
: base(directory, extension, continuousCapture) { }

/// <summary>
/// Creates a new instance of the <see cref="ImageStreamCaptureHandler"/> class with the specified file path.
/// Creates a new instance of the <see cref="ImageStreamCaptureHandler"/> class with the specified file pathname. An auto-incrementing number is added to each
/// new filename.
/// </summary>
/// <param name="fullPath">The absolute full path to save captured data to.</param>
public ImageStreamCaptureHandler(string fullPath)
: base(fullPath) { }
/// <param name="continuousCapture">When true, every frame is written to a file.</param>
public ImageStreamCaptureHandler(string fullPath, bool continuousCapture = true)
: base(fullPath, continuousCapture) { }
}
}
4 changes: 2 additions & 2 deletions src/MMALSharp/Callbacks/FastImageOutputCallbackHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ public override void Callback(IBuffer buffer)
var eos = buffer.AssertProperty(MMALBufferProperties.MMAL_BUFFER_HEADER_FLAG_FRAME_END) ||
buffer.AssertProperty(MMALBufferProperties.MMAL_BUFFER_HEADER_FLAG_EOS);

if (eos && this.CaptureHandler is IFileStreamCaptureHandler)
if (eos)
{
((IFileStreamCaptureHandler)this.CaptureHandler).NewFile();
(this.CaptureHandler as IFileStreamCaptureHandler)?.NewFrame();
}
}
}
Expand Down