Simplified creation of new capture objects

This commit is contained in:
Joel Collins 2019-04-26 18:26:27 +01:00
parent 28561433a0
commit ea44bbefd2
2 changed files with 37 additions and 85 deletions

View file

@ -316,18 +316,20 @@ class BaseCamera(object):
if not filename: if not filename:
filename = generate_numbered_basename(self.images) filename = generate_numbered_basename(self.images)
logging.debug(filename) logging.debug(filename)
filename = "{}.{}".format(filename, fmt)
# Generate folder # Generate folder
base_folder = self.paths['image_tmp'] if temporary else self.paths['image'] base_folder = self.paths['image_tmp'] if temporary else self.paths['image']
folder = os.path.join(base_folder, folder) folder = os.path.join(base_folder, folder)
# Generate file path
filepath = os.path.join(folder, filename)
# Create capture object # Create capture object
output = CaptureObject( output = CaptureObject(
write_to_file=write_to_file, write_to_file=write_to_file,
temporary=temporary, temporary=temporary,
filename=filename, filepath=filepath)
folder=folder,
fmt=fmt)
# Update capture list # Update capture list
self.shunt_captures(self.images) self.shunt_captures(self.images)
@ -360,18 +362,20 @@ class BaseCamera(object):
if not filename: if not filename:
filename = generate_numbered_basename(self.videos) filename = generate_numbered_basename(self.videos)
logging.debug(filename) logging.debug(filename)
filename = "{}.{}".format(filename, fmt)
# Generate folder # Generate folder
base_folder = self.paths['video_tmp'] if temporary else self.paths['video'] base_folder = self.paths['video_tmp'] if temporary else self.paths['video']
folder = os.path.join(base_folder, folder) folder = os.path.join(base_folder, folder)
# Generate file path
filepath = os.path.join(folder, filename)
# Create capture object # Create capture object
output = CaptureObject( output = CaptureObject(
write_to_file=write_to_file, write_to_file=write_to_file,
temporary=temporary, temporary=temporary,
filename=filename, filepath=filepath)
folder=folder,
fmt=fmt)
# Update capture list # Update capture list
self.shunt_captures(self.videos) self.shunt_captures(self.videos)

View file

@ -18,7 +18,7 @@ THUMBNAIL_SIZE = (200, 150)
BASE_CAPTURE_PATH = os.path.join(os.path.expanduser('~'), 'micrographs') #: str: Base path to store all captures BASE_CAPTURE_PATH = os.path.join(os.path.expanduser('~'), 'micrographs') #: str: Base path to store all captures
TEMP_CAPTURE_PATH = os.path.join(BASE_CAPTURE_PATH, 'tmp') #: str: Base path to store all temporary captures (automatically emptied) TEMP_CAPTURE_PATH = os.path.join(BASE_CAPTURE_PATH, 'tmp') #: str: Base path to store all temporary captures (automatically emptied)
# TODO: Move these methods to a camera utilities module?
def clear_tmp(): def clear_tmp():
""" """
Removes all files in the ``TEMP_CAPTURE_PATH`` directory Removes all files in the ``TEMP_CAPTURE_PATH`` directory
@ -75,11 +75,9 @@ def capture_from_dict(capture_dict):
""" """
global EXIF_FORMATS global EXIF_FORMATS
capture = CaptureObject() # Create a placeholder capture capture = CaptureObject(
filepath=capture_dict['path']
# Get inherent capture information from database ) # Create a placeholder capture
capture.file = capture_dict['path']
capture.temporary = capture_dict['temporary']
capture.split_file_path(capture.file) capture.split_file_path(capture.file)
if capture.format.upper() in EXIF_FORMATS: if capture.format.upper() in EXIF_FORMATS:
@ -111,9 +109,7 @@ class CaptureObject(object):
self, self,
write_to_file: bool = False, write_to_file: bool = False,
temporary: bool = False, temporary: bool = False,
filename: str = '', filepath: str = '') -> None:
folder: str = '',
fmt: str = '') -> None:
"""Create a new StreamObject, to manage capture data.""" """Create a new StreamObject, to manage capture data."""
# Store a nice ID # Store a nice ID
@ -121,22 +117,15 @@ class CaptureObject(object):
logging.info("Created StreamObject {}".format(self.id)) logging.info("Created StreamObject {}".format(self.id))
self.timestring = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S") #: str: Timestring of capture creation time self.timestring = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S") #: str: Timestring of capture creation time
# Store file format
self.format = fmt #: str: Capture data format (e.g. jpeg, h264)
# Keep on disk after close by default # Keep on disk after close by default
self.temporary = temporary #: bool: Mark the capture as temporary, to be deleted as the server closes, or as resources are required self.temporary = temporary #: bool: Mark the capture as temporary, to be deleted as the server closes, or as resources are required
# Explicitally state if capture should be written to a file, not a bytestream
self.write_to_file = write_to_file #: bool: Write capture data to disk immediately, rather than to memory initially (useful for video recordings)
# Create file name. Default to UUID # Create file name. Default to UUID
if not filename: if not filepath:
filename = self.id self.file = self.build_file_path()
self.filename = "{}.{}".format(filename, fmt) else:
self.file = filepath
# Create folder path self.split_file_path(self.file)
self.folder = folder
# Dictionary for storing custom metadata # Dictionary for storing custom metadata
self._metadata = {} #: dict: Dictionary of custom metadata to be included in metadata file self._metadata = {} #: dict: Dictionary of custom metadata to be included in metadata file
@ -144,15 +133,18 @@ class CaptureObject(object):
# List for storing tags # List for storing tags
self.tags = [] #: list: List of tags. Essentially just as extra custom metadata field, but useful for quick organisation self.tags = [] #: list: List of tags. Essentially just as extra custom metadata field, but useful for quick organisation
# Initialise the capture stream # Byte bytestream properties
self.initialise_capture() self.bytestream = io.BytesIO() # Byte bytestream that data will be written to
# Set default write target
if not write_to_file:
self.stream = self.bytestream
else:
self.stream = self.file
# Log if created by context manager # Log if created by context manager
self.context_manager = False self.context_manager = False
# Object lock
self.locked = False
# Thumbnail (populated only for PIL captures) # Thumbnail (populated only for PIL captures)
self.thumb_bytes = None self.thumb_bytes = None
@ -165,7 +157,7 @@ class CaptureObject(object):
self.context_manager = True # Used in metadata self.context_manager = True # Used in metadata
logging.info("Rebuilding as a temporary capture...") logging.info("Rebuilding as a temporary capture...")
self.initialise_capture() self.build_file_path()
return self return self
@ -174,52 +166,13 @@ class CaptureObject(object):
logging.info("Cleaning up {}".format(self.id)) logging.info("Cleaning up {}".format(self.id))
self.close() self.close()
def initialise_capture(self):
"""
Initialise the capture object. Creates a file name and builds a file path,
creates an in-memory byte stream for capture data, and creates a metadata
file on disk.
"""
self.build_file_path()
# Byte bytestream properties
self.bytestream = io.BytesIO() # Byte bytestream that data will be written to
# Set default write target
if not self.write_to_file:
logging.debug("Target for {} set to 'bytestream'".format(self.id))
self.stream = self.bytestream
else:
logging.debug("Target for {} set to 'file'".format(self.id))
self.stream = self.file
# Save initial metadata file
self.save_metadata()
def build_file_path(self): def build_file_path(self):
""" """
Construct a full file path, based on filename, folder, and file format. Construct a full file path, based on filename, folder, and file format.
Defaults to UUID. Defaults to UUID.
""" """
global TEMP_CAPTURE_PATH global TEMP_CAPTURE_PATH
# TODO: Combine this and split_file_path, and tidy. Let the device (camera) handle folders. This should be more basic. return os.path.join(self.folder, self.filename) # Full file name by joining given folder to given name
# TODO: Even let the base camera manage moving captures to temp folder
# This module will clear out the temp folder, but won't MOVE anything there.
# In Base cameras new_image method, the full folder is constructed. Temp folder should be inserted there.
self.file = os.path.join(self.folder, self.filename) # Full file name by joining given folder to given name
self.split_file_path(self.file) # Split file path into folder, filename, and basename
# Check directory is a subdirectory of BASE_CAPTURE_PATH
# TODO: Do we need this?
if not os.path.commonprefix([self.file, BASE_CAPTURE_PATH]) == BASE_CAPTURE_PATH:
raise Exception("Captures cannot be stored in a lower-level directory than {}.".format(BASE_CAPTURE_PATH))
# Create folder and file
if not os.path.exists(self.filefolder):
os.makedirs(self.filefolder)
def split_file_path(self, filepath): def split_file_path(self, filepath):
""" """
@ -230,17 +183,11 @@ class CaptureObject(object):
""" """
self.filefolder, self.filename = os.path.split(filepath) # Split the full file path into a folder and a filename self.filefolder, self.filename = os.path.split(filepath) # Split the full file path into a folder and a filename
self.basename = os.path.splitext(self.filename)[0] # Split the filename out from it's file extension self.basename = os.path.splitext(self.filename)[0] # Split the filename out from it's file extension
self.format = self.filename.split('.')[-1]
if not self.format: # Create folder and file
self.format = self.filename.split('.')[-1] if not os.path.exists(self.filefolder):
os.makedirs(self.filefolder)
def lock(self):
"""Set locked flag to True."""
self.locked = True
def unlock(self):
"""Set locked flag to False."""
self.locked = False
@property @property
def stream_exists(self, auto_rewind=True) -> bool: def stream_exists(self, auto_rewind=True) -> bool:
@ -325,7 +272,7 @@ class CaptureObject(object):
Create basic metadata dictionary from basic capture data, Create basic metadata dictionary from basic capture data,
and any added custom metadata and tags. and any added custom metadata and tags.
""" """
d = {'id': self.id, 'filename': self.filename, 'path': self.file, 'time': self.timestring, d = {'id': self.id, 'filename': self.filename, 'time': self.timestring,
'format': self.format, 'tags': self.tags, 'custom': self._metadata} 'format': self.format, 'tags': self.tags, 'custom': self._metadata}
# Add custom metadata to dictionary # Add custom metadata to dictionary
@ -354,7 +301,7 @@ class CaptureObject(object):
""" """
# Create basic state dictionary # Create basic state dictionary
d = {'path': self.file, 'locked': self.locked, 'temporary': self.temporary, 'metadata': self.metadata} d = {'path': self.file, 'temporary': self.temporary, 'metadata': self.metadata}
# Check bytestream # Check bytestream
if self.stream_exists: if self.stream_exists:
@ -382,6 +329,7 @@ class CaptureObject(object):
if self.stream_exists: # If data bytestream contains data if self.stream_exists: # If data bytestream contains data
# Create a copy of the bytestream bytes # Create a copy of the bytestream bytes
logging.debug("STREAM EXISTS")
data = io.BytesIO(self.bytestream.getbuffer()) data = io.BytesIO(self.bytestream.getbuffer())
else: # If data bytestream is empty else: # If data bytestream is empty