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

Better handling of capture path and temporary files

parent afc75b80
Loading
Loading
Loading
Loading
+6 −1
Original line number Diff line number Diff line
# -*- coding: utf-8 -*-
import time
import io
import os
import threading
from PIL import Image
import datetime
@@ -14,7 +15,7 @@ except ImportError:
    except ImportError:
        from _thread import get_ident

from .capture import StreamObject
from .capture import StreamObject, BASE_CAPTURE_PATH


def last_entry(object_list: list):
@@ -97,6 +98,10 @@ class BaseCamera(object):

        self.state = {}  #: dict: Dictionary for capture state
        self.settings = {}  #: dict: Dictionary of camera settings
        self.paths = {
            'image': BASE_CAPTURE_PATH,
            'video': BASE_CAPTURE_PATH,
        }  #: dict: Dictionary of capture paths

        # Capture data
        self.images = []  #: list: List of image capture objects
+35 −21
Original line number Diff line number Diff line
@@ -9,18 +9,22 @@ from PIL import Image
pil_formats = ['JPG', 'JPEG', 'PNG', 'TIF', 'TIFF']
thumbnail_size = (60, 60)

BASE_CAPTURE_PATH = os.path.join(os.path.expanduser('~'), 'micrographs')
TEMP_CAPTURE_PATH = os.path.join(BASE_CAPTURE_PATH, 'tmp')

class StreamObject(object):
    """
    StreamObject used to store and process capture data, and metadata.

    Note: Captures cannot be stored in a lower-level directory than BASE_CAPTURE_PATH.
    """
    def __init__(
            self,
            write_to_file: bool=False,
            keep_on_disk: bool=True,
            filename: str=None,
            folder: str=None,
            fmt: str='file') -> None:
            filename: str='',
            folder: str='',
            fmt: str='') -> None:
        """Create a new StreamObject, to manage capture data."""
        # Store a nice ID
        self.id = uuid.uuid4().hex
@@ -80,32 +84,35 @@ class StreamObject(object):
        """
        Construct a full file path, based on filename, folder, and file format.

        Defaults to datestamp.
        Defaults to UUID.
        """
        global TEMP_CAPTURE_PATH

        base_name = filename
        file_name = "{}.{}".format(base_name, fmt)
        file_given_name = "{}.{}".format(filename, fmt)  # Given file name. May include a relative path

        # Create folder and file
        if folder:
            if not os.path.exists(folder):
                os.mkdir(folder)
        self.file = os.path.join(folder, file_given_name)  # Full file name by joining given folder to given name

            file_path = os.path.join(folder, file_name)
        else:
            file_path = file_name
        self.split_file_path(self.file)  # Split file path into folder, filename, and basename

        # Handle path appendix
        appendix = ""
        # Check directory is a subdirectory of BASE_CAPTURE_PATH
        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))

        # Move to tmp directory if not being kept
        if not self.keep_on_disk:
            appendix += ".tmp"
            # TODO: Empty tmp folder on module load and atexit
            self.file_notmp = self.file  # Store originally defined folder
            self.filefolder = TEMP_CAPTURE_PATH
            self.file = os.path.join(self.filefolder, self.filename)

        file_path = file_path + appendix
        # Create folder and file
        if not os.path.exists(self.filefolder):
            os.mkdir(self.filefolder)

        #self.basename = filename
        self.file = file_path
        self.filename = file_name
        self.basename = base_name
    def split_file_path(self, filepath):
        """Takes a full file path, and splits it into separated class properties."""
        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

    def lock(self):
        """Set locked flag to True."""
@@ -223,6 +230,13 @@ class StreamObject(object):

    def save_file(self) -> bool:
        """Write the StreamObjects stream to a file."""
        if not self.keep_on_disk:  # If capture is currently temporary
            self.load_file()  # Load data from tmp file into stream, if tmp file exists
            self.keep_on_disk = True  # Flag as kept on disk
            self.file = self.file_notmp  # Reset file path to non-temporary path
            self.split_file_path(self.file)  # Set split properties based on new path
            logging.info("Moved temporary file out to {}".format(self.file))

        if self.stream_exists:  # If there's a stream to save
            with open(self.file, 'ab') as f:  # Load file as bytes
                logging.debug("Writing stream to file {}".format(self.file))
+2 −6
Original line number Diff line number Diff line
@@ -214,7 +214,6 @@ class StreamingCamera(BaseCamera):
            target=None,
            write_to_file: bool=True,
            filename: str=None,
            folder: str='record',
            fmt: str='h264',
            quality: int=15):
        """Start recording.
@@ -225,7 +224,6 @@ class StreamingCamera(BaseCamera):
            target (str/BytesIO): Target object to write bytes to.
            write_to_file (bool/NoneType): Should the StreamObject write to a file?
            filename (str): Name of the stored file.  Defaults to timestamp.
            folder (str): Relative directory to store data file in.
            fmt (str): Format of the capture.

        Returns:
@@ -242,7 +240,7 @@ class StreamingCamera(BaseCamera):
                    StreamObject(
                        write_to_file=write_to_file,
                        filename=filename,
                        folder=folder,
                        folder=self.paths['video'],
                        fmt=fmt)
                    )

@@ -339,7 +337,6 @@ class StreamingCamera(BaseCamera):
            keep_on_disk: bool=True,
            use_video_port: bool=False,
            filename: str=None,
            folder: str='capture',
            fmt: str='jpeg',
            resize: Tuple[int, int]=None):
        """
@@ -353,7 +350,6 @@ class StreamingCamera(BaseCamera):
            write_to_file (bool): Should the StreamObject write to a file, instead of BytesIO stream?
            use_video_port (bool): Capture from the video port used for streaming. Lower resolution, faster.
            filename (str): Name of the stored file. Defaults to timestamp.
            folder (str): Relative directory to store data file in.
            fmt (str): Format of the capture.
            resize ((int, int)): Resize the captured image.
        """
@@ -372,7 +368,7 @@ class StreamingCamera(BaseCamera):
                    write_to_file=write_to_file,
                    keep_on_disk=keep_on_disk,
                    filename=filename,
                    folder=folder,
                    folder=self.paths['image'],
                    fmt=fmt)
                )
            target = target_obj.target  # Store to the StreamObject BytesIO