Commit ea44bbef authored by Joel Collins's avatar Joel Collins
Browse files

Simplified creation of new capture objects

parent 28561433
Loading
Loading
Loading
Loading
+10 −6
Original line number Diff line number Diff line
@@ -316,18 +316,20 @@ class BaseCamera(object):
        if not filename:
            filename = generate_numbered_basename(self.images)
            logging.debug(filename)
        filename = "{}.{}".format(filename, fmt)

        # Generate folder
        base_folder = self.paths['image_tmp'] if temporary else self.paths['image']
        folder = os.path.join(base_folder, folder)

        # Generate file path
        filepath = os.path.join(folder, filename)

        # Create capture object
        output = CaptureObject(
            write_to_file=write_to_file,
            temporary=temporary,
            filename=filename,
            folder=folder,
            fmt=fmt)
            filepath=filepath)

        # Update capture list
        self.shunt_captures(self.images)
@@ -360,18 +362,20 @@ class BaseCamera(object):
        if not filename:
            filename = generate_numbered_basename(self.videos)
            logging.debug(filename)
        filename = "{}.{}".format(filename, fmt)

        # Generate folder
        base_folder = self.paths['video_tmp'] if temporary else self.paths['video']
        folder = os.path.join(base_folder, folder)

        # Generate file path
        filepath = os.path.join(folder, filename)

        # Create capture object
        output = CaptureObject(
            write_to_file=write_to_file,
            temporary=temporary,
            filename=filename,
            folder=folder,
            fmt=fmt)
            filepath=filepath)

        # Update capture list
        self.shunt_captures(self.videos)
+27 −79
Original line number Diff line number Diff line
@@ -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
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():
    """
    Removes all files in the ``TEMP_CAPTURE_PATH`` directory
@@ -75,11 +75,9 @@ def capture_from_dict(capture_dict):
    """
    global EXIF_FORMATS

    capture = CaptureObject()  # Create a placeholder capture

    # Get inherent capture information from database
    capture.file = capture_dict['path']
    capture.temporary = capture_dict['temporary']
    capture = CaptureObject(
        filepath=capture_dict['path']
    )  # Create a placeholder capture
    capture.split_file_path(capture.file)

    if capture.format.upper() in EXIF_FORMATS:
@@ -111,9 +109,7 @@ class CaptureObject(object):
            self,
            write_to_file: bool = False,
            temporary: bool = False,
            filename: str = '',
            folder: str = '',
            fmt: str = '') -> None:
            filepath: str = '') -> None:
        """Create a new StreamObject, to manage capture data."""

        # Store a nice ID
@@ -121,22 +117,15 @@ class CaptureObject(object):
        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

        # Store file format
        self.format = fmt  #: str: Capture data format (e.g. jpeg, h264)

        # 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

        # 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
        if not filename:
            filename = self.id
        self.filename = "{}.{}".format(filename, fmt)

        # Create folder path
        self.folder = folder
        if not filepath:
            self.file = self.build_file_path()
        else:
            self.file = filepath
        self.split_file_path(self.file)

        # Dictionary for storing custom metadata
        self._metadata = {}  #: dict: Dictionary of custom metadata to be included in metadata file
@@ -144,15 +133,18 @@ class CaptureObject(object):
        # List for storing tags
        self.tags = []  #: list: List of tags. Essentially just as extra custom metadata field, but useful for quick organisation

        # Initialise the capture stream
        self.initialise_capture()
        # Byte bytestream properties
        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
        self.context_manager = False

        # Object lock
        self.locked = False

        # Thumbnail (populated only for PIL captures)
        self.thumb_bytes = None

@@ -165,7 +157,7 @@ class CaptureObject(object):
        self.context_manager = True  # Used in metadata

        logging.info("Rebuilding as a temporary capture...")
        self.initialise_capture()
        self.build_file_path()

        return self

@@ -174,52 +166,13 @@ class CaptureObject(object):
        logging.info("Cleaning up {}".format(self.id))
        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):
        """
        Construct a full file path, based on filename, folder, and file format.
        Defaults to UUID.
        """
        global TEMP_CAPTURE_PATH
        # TODO: Combine this and split_file_path, and tidy. Let the device (camera) handle folders. This should be more basic.
        # 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)
        return os.path.join(self.folder, self.filename)  # Full file name by joining given folder to given name

    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.basename = os.path.splitext(self.filename)[0]  # Split the filename out from it's file extension

        if not self.format:
        self.format = self.filename.split('.')[-1]

    def lock(self):
        """Set locked flag to True."""
        self.locked = True

    def unlock(self):
        """Set locked flag to False."""
        self.locked = False
        # Create folder and file
        if not os.path.exists(self.filefolder):
            os.makedirs(self.filefolder)

    @property
    def stream_exists(self, auto_rewind=True) -> bool:
@@ -325,7 +272,7 @@ class CaptureObject(object):
        Create basic metadata dictionary from basic capture data, 
        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}

        # Add custom metadata to dictionary
@@ -354,7 +301,7 @@ class CaptureObject(object):
        """

        # 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
        if self.stream_exists:
@@ -382,6 +329,7 @@ class CaptureObject(object):

        if self.stream_exists:  # If data bytestream contains data
            # Create a copy of the bytestream bytes
            logging.debug("STREAM EXISTS")
            data = io.BytesIO(self.bytestream.getbuffer())

        else:  # If data bytestream is empty