diff --git a/src/MMALSharp.Processing/Handlers/FileStreamCaptureHandler.cs b/src/MMALSharp.Processing/Handlers/FileStreamCaptureHandler.cs index 3f4f8c74..6bdbd323 100644 --- a/src/MMALSharp.Processing/Handlers/FileStreamCaptureHandler.cs +++ b/src/MMALSharp.Processing/Handlers/FileStreamCaptureHandler.cs @@ -9,16 +9,47 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Runtime.CompilerServices; namespace MMALSharp.Handlers { /// /// Processes image data to a . /// - public class FileStreamCaptureHandler : StreamCaptureHandler, IFileStreamCaptureHandler + public class FileStreamCaptureHandler : MemoryStreamCaptureHandler, IFileStreamCaptureHandler { private readonly bool _customFilename; private int _increment; + private bool _skippingFirstPartialFrame = true; + private bool _continuousCapture; + + /// + /// When true, the next full frame will be written. If is not also + /// true, this property will be reset to false after writing the image so that only one image is written. + /// + public bool CaptureNextFrame { get; set; } + + /// + /// When true, every frame is written to storage. + /// + public bool ContinuousCapture + { + get => _continuousCapture; + + set + { + _continuousCapture = value; + if(_continuousCapture) + { + CaptureNextFrame = true; + } + } + } + + /// + /// Defines the image files' DateTime format string that is applied when the object is constructed with directory and extension arguments. + /// + public string FilenameDateTimeFormat { get; set; } = "dd-MMM-yy HH-mm-ss"; /// /// A list of files that have been processed by this capture handler. @@ -36,10 +67,15 @@ public class FileStreamCaptureHandler : StreamCaptureHandler, IFileS public string Extension { get; set; } /// - /// The name of the current file associated with the FileStream. + /// The filename to write next (if applicable). /// public string CurrentFilename { get; set; } + /// + /// The full pathname of the most recently written image file (if any). + /// + public string LastWrittenPathname { get; set; } + /// /// Creates a new instance of the class without provisions for writing to a file. Supports /// subclasses in which file output is optional. @@ -51,82 +87,73 @@ public FileStreamCaptureHandler() /// /// Creates a new instance of the 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 property. /// /// The directory to save captured data. /// The filename extension for saving files. - public FileStreamCaptureHandler(string directory, string extension) + /// When true, every frame is written to a file. + 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(); } /// - /// Creates a new instance of the class with the specified file path. + /// Creates a new instance of the class with the specified file pathname. An auto-incrementing number is added to each + /// new filename. /// /// The absolute full path to save captured data to. - public FileStreamCaptureHandler(string fullPath) + /// When true, every frame is written to a file. + 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(); } /// - /// Gets the filename that a FileStream points to. + /// Gets the filename of the most recently stored image file. /// /// The filename. - 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; /// - /// Gets the filepath that a FileStream points to. + /// Gets the pathname of the most recently stored image file. /// /// The filepath. - public string GetFilepath() => - this.CurrentStream?.Name ?? string.Empty; + public string GetFilepath() => + this.LastWrittenPathname; /// - /// 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. /// public virtual void NewFile() { @@ -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}"; @@ -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; + } + + /// + /// If capture is active, output a new file. + /// + 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); + } } /// diff --git a/src/MMALSharp.Processing/Handlers/IFileStreamCaptureHandler.cs b/src/MMALSharp.Processing/Handlers/IFileStreamCaptureHandler.cs index c708f12c..bfc203a1 100644 --- a/src/MMALSharp.Processing/Handlers/IFileStreamCaptureHandler.cs +++ b/src/MMALSharp.Processing/Handlers/IFileStreamCaptureHandler.cs @@ -15,6 +15,11 @@ public interface IFileStreamCaptureHandler : IOutputCaptureHandler /// void NewFile(); + /// + /// The callback handler has received an end-of-stream marker. + /// + void NewFrame(); + /// /// Gets the filepath that a FileStream points to. /// diff --git a/src/MMALSharp.Processing/Handlers/ImageStreamCaptureHandler.cs b/src/MMALSharp.Processing/Handlers/ImageStreamCaptureHandler.cs index 9d8599ad..88fbf9a3 100644 --- a/src/MMALSharp.Processing/Handlers/ImageStreamCaptureHandler.cs +++ b/src/MMALSharp.Processing/Handlers/ImageStreamCaptureHandler.cs @@ -13,18 +13,22 @@ namespace MMALSharp.Handlers public class ImageStreamCaptureHandler : FileStreamCaptureHandler { /// - /// Creates a new instance of the class with the specified directory and filename extension. + /// Creates a new instance of the class with the specified directory and filename extension. Filenames will be in the + /// format defined by the property. /// /// The directory to save captured images. /// The filename extension for saving files. - public ImageStreamCaptureHandler(string directory, string extension) - : base(directory, extension) { } + /// When true, every frame is written to a file. + public ImageStreamCaptureHandler(string directory, string extension, bool continuousCapture = true) + : base(directory, extension, continuousCapture) { } /// - /// Creates a new instance of the class with the specified file path. + /// Creates a new instance of the class with the specified file pathname. An auto-incrementing number is added to each + /// new filename. /// /// The absolute full path to save captured data to. - public ImageStreamCaptureHandler(string fullPath) - : base(fullPath) { } + /// When true, every frame is written to a file. + public ImageStreamCaptureHandler(string fullPath, bool continuousCapture = true) + : base(fullPath, continuousCapture) { } } } diff --git a/src/MMALSharp/Callbacks/FastImageOutputCallbackHandler.cs b/src/MMALSharp/Callbacks/FastImageOutputCallbackHandler.cs index 487b6ea4..735edc93 100644 --- a/src/MMALSharp/Callbacks/FastImageOutputCallbackHandler.cs +++ b/src/MMALSharp/Callbacks/FastImageOutputCallbackHandler.cs @@ -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(); } } }