Implemented root API v2 routes

This commit is contained in:
jtc42 2019-11-14 16:33:00 +00:00
parent 5aa783c269
commit 6581612312
27 changed files with 1205 additions and 148 deletions

View file

@ -100,7 +100,7 @@ class BaseCamera(metaclass=ABCMeta):
last_access (time): Time of last client access to the camera
stream_timeout (int): Number of inactive seconds before timing out the stream
stream_timeout_enabled (bool): Enable or disable timing out the stream
state (dict): Dictionary for capture state
status (dict): Dictionary for capture state
paths (dict): Dictionary of capture paths
images (list): List of image capture objects
videos (list): List of video capture objects
@ -120,7 +120,7 @@ class BaseCamera(metaclass=ABCMeta):
self.stream_timeout = 20
self.stream_timeout_enabled = False
self.state = {"board": None}
self.status = {"board": None}
# TODO: Load/save these to config
self.paths = {"default": BASE_CAPTURE_PATH, "temp": TEMP_CAPTURE_PATH}
@ -130,7 +130,7 @@ class BaseCamera(metaclass=ABCMeta):
self.videos = []
@abstractmethod
def apply_config(self, config: dict):
def apply_settings(self, config: dict):
"""Update settings from a config dictionary"""
with self.lock:
# Apply valid config params to camera object
@ -139,11 +139,11 @@ class BaseCamera(metaclass=ABCMeta):
setattr(self, key, value) # Set to the target value
@abstractmethod
def read_config(self) -> dict:
def read_settings(self) -> dict:
"""Return the current settings as a dictionary"""
return {"paths": self.paths}
def save_config(self):
def save_settings(self):
"""(Optional) Save any settings to disk that need to be stored"""
return
@ -187,7 +187,7 @@ class BaseCamera(metaclass=ABCMeta):
self.last_access = time.time()
self.stop = False
if not self.state["stream_active"]:
if not self.status["stream_active"]:
# start background frame thread
self.thread = threading.Thread(target=self._thread)
self.thread.daemon = True
@ -207,12 +207,12 @@ class BaseCamera(metaclass=ABCMeta):
logging.debug("Stopping worker thread")
timeout_time = time.time() + timeout
if self.state["stream_active"]:
if self.status["stream_active"]:
self.stop = True
self.thread.join() # Wait for stream thread to exit
logging.debug("Waiting for stream thread to exit.")
while self.state["stream_active"]:
while self.status["stream_active"]:
if time.time() > timeout_time:
logging.debug("Timeout waiting for worker thread close.")
raise TimeoutError("Timeout waiting for worker thread close.")
@ -352,7 +352,7 @@ class BaseCamera(metaclass=ABCMeta):
self.frames_iterator = self.frames()
logging.debug("Entering worker thread.")
self.state["stream_active"] = True
self.status["stream_active"] = True
for frame in self.frames_iterator:
self.frame = frame
@ -365,7 +365,7 @@ class BaseCamera(metaclass=ABCMeta):
and ( # If using timeout
time.time() - self.last_access > self.stream_timeout
)
and not self.state[ # And timeout time
and not self.status[ # And timeout time
"preview_active"
] # And GPU preview is not active
):
@ -383,4 +383,4 @@ class BaseCamera(metaclass=ABCMeta):
logging.debug("BaseCamera worker thread exiting...")
# Set stream_activate state
self.state["stream_active"] = False
self.status["stream_active"] = False

View file

@ -97,7 +97,7 @@ def capture_from_exif(path, exif_dict):
capture.timestring = exif_dict["time"]
capture.format = exif_dict["format"]
capture._metadata = exif_dict["custom"]
capture.custom_metadata = exif_dict["custom"]
capture.tags = exif_dict["tags"]
return capture
@ -110,7 +110,7 @@ class CaptureObject(object):
Attributes:
timestring (str): Timestring of capture creation time
_metadata (dict): Dictionary of custom metadata to be included in metadata file
custom_metadata (dict): Dictionary of custom metadata to be included in metadata file
tags (list): List of tags. Essentially just as extra custom metadata field, but useful for quick organisation
filefolder (str): Folder in which the capture file will be stored
filename (str): Full name of the capture file
@ -132,7 +132,9 @@ class CaptureObject(object):
self.split_file_path(self.file)
# Dictionary for storing custom metadata
self._metadata = {}
self.custom_metadata = {}
# Dictionary for adding top-level metadata (cannmot be accessed through web API)
self.system_metadata = {}
# List for storing tags
self.tags = []
@ -203,7 +205,7 @@ class CaptureObject(object):
Args:
data (dict): Dictionary of metadata to be added
"""
self._metadata.update(data)
self.custom_metadata.update(data)
self.save_metadata()
def save_metadata(self) -> None:
@ -225,6 +227,7 @@ class CaptureObject(object):
# Insert exif into file
piexif.insert(exif_bytes, self.file)
@property
def metadata(self) -> dict:
"""
@ -233,13 +236,14 @@ class CaptureObject(object):
"""
d = {
"id": self.id,
"filename": self.filename,
"time": self.timestring,
"format": self.format,
"tags": self.tags,
"custom": self._metadata,
"custom": self.custom_metadata,
}
d.update(self.system_metadata)
# Add custom metadata to dictionary
return d
@ -250,7 +254,7 @@ class CaptureObject(object):
"""
# Create basic state dictionary
d = {"path": self.file, "metadata": self.metadata}
d = {"path": self.file, "filename": self.filename, "metadata": self.metadata}
# Combined availability of data
if self.exists:

View file

@ -26,7 +26,7 @@ class MockStreamer(BaseCamera):
BaseCamera.__init__(self)
# Store state of PiCameraStreamer
self.state.update(
self.status.update(
{"stream_active": False, "record_active": False, "board": None}
)
@ -74,13 +74,13 @@ class MockStreamer(BaseCamera):
BaseCamera.close(self)
# HANDLE SETTINGS
def read_config(self) -> dict:
def read_settings(self) -> dict:
"""
Return config dictionary of the PiCameraStreamer.
"""
# Get config items from the base class
conf_dict = BaseCamera.read_config(self)
conf_dict = BaseCamera.read_settings(self)
# Include device-specific config items
conf_dict.update(
@ -94,7 +94,7 @@ class MockStreamer(BaseCamera):
return conf_dict
def apply_config(self, config: dict):
def apply_settings(self, config: dict):
"""
Write a config dictionary to the PiCameraStreamer config.
@ -110,7 +110,7 @@ class MockStreamer(BaseCamera):
with self.lock:
# Apply valid config params to camera object
if not self.state["record_active"]: # If not recording a video
if not self.status["record_active"]: # If not recording a video
for key, value in config.items(): # For each provided setting
if hasattr(self, key):

View file

@ -75,8 +75,8 @@ class PiCameraStreamer(BaseCamera):
picamera.PiCamera()
) #: :py:class:`picamera.PiCamera`: Picamera object
# Store state of PiCameraStreamer
self.state.update(
# Store status of PiCameraStreamer
self.status.update(
{
"stream_active": False,
"record_active": False,
@ -97,7 +97,7 @@ class PiCameraStreamer(BaseCamera):
self.picamera_lst_path = settings_file_path("picamera_lst.npy") #: str: Path of .npy lens shading table file
# Update board identifier
self.state.update({})
self.status.update({})
# Create an empty stream
self.stream = io.BytesIO()
@ -118,13 +118,13 @@ class PiCameraStreamer(BaseCamera):
self.camera.close()
# HANDLE SETTINGS
def read_config(self) -> dict:
def read_settings(self) -> dict:
"""
Return config dictionary of the PiCameraStreamer.
"""
# Get config items from the base class
conf_dict = BaseCamera.read_config(self)
conf_dict = BaseCamera.read_settings(self)
# Include device-specific config items
conf_dict.update(
@ -149,12 +149,12 @@ class PiCameraStreamer(BaseCamera):
return conf_dict
def save_config(self):
def save_settings(self):
"""Save lens-shading table to disk"""
logging.info("Saving picamera_lst to {}".format(self.picamera_lst_path))
self.save_lens_shading_table()
def apply_config(self, config: dict):
def apply_settings(self, config: dict):
"""
Write a config dictionary to the PiCameraStreamer config.
@ -172,10 +172,10 @@ class PiCameraStreamer(BaseCamera):
with self.lock:
# Apply valid config params to Picamera object
if not self.state["record_active"]: # If not recording a video
if not self.status["record_active"]: # If not recording a video
# Pause stream while changing settings
if self.state["stream_active"]: # If stream is active
if self.status["stream_active"]: # If stream is active
logging.info("Pausing stream to update config.")
self.stop_stream_recording() # Pause stream
paused_stream = True # Remember to unpause stream when done
@ -299,13 +299,13 @@ class PiCameraStreamer(BaseCamera):
Change the camera zoom, handling re-centering and scaling.
"""
with self.lock:
self.state["zoom_value"] = float(zoom_value)
if self.state["zoom_value"] < 1:
self.state["zoom_value"] = 1
self.status["zoom_value"] = float(zoom_value)
if self.status["zoom_value"] < 1:
self.status["zoom_value"] = 1
# Richard's code for zooming !
fov = self.camera.zoom
centre = np.array([fov[0] + fov[2] / 2.0, fov[1] + fov[3] / 2.0])
size = 1.0 / self.state["zoom_value"]
size = 1.0 / self.status["zoom_value"]
# If the new zoom value would be invalid, move the centre to
# keep it within the camera's sensor (this is only relevant
# when zooming out, if the FoV is not centred on (0.5, 0.5)
@ -332,7 +332,7 @@ class PiCameraStreamer(BaseCamera):
self.camera.preview.window = window
if fullscreen:
self.camera.preview.fullscreen = fullscreen
self.state["preview_active"] = True
self.status["preview_active"] = True
except picamera.exc.PiCameraMMALError as e:
logging.error(
"Suppressed a MMALError in start_preview. Exception: {}".format(e)
@ -347,7 +347,7 @@ class PiCameraStreamer(BaseCamera):
def stop_preview(self):
"""Stop the on board GPU camera preview."""
self.camera.stop_preview()
self.state["preview_active"] = False
self.status["preview_active"] = False
def start_recording(self, output, fmt: str = "h264", quality: int = 15):
"""Start recording.
@ -365,7 +365,7 @@ class PiCameraStreamer(BaseCamera):
"""
with self.lock:
# Start recording method only if a current recording is not running
if not self.state["record_active"]:
if not self.status["record_active"]:
# Start the camera video recording on port 2
logging.info("Recording to {}".format(output))
@ -378,8 +378,8 @@ class PiCameraStreamer(BaseCamera):
quality=quality,
)
# Update state dictionary
self.state["record_active"] = True
# Update status dictionary
self.status["record_active"] = True
return output
@ -398,8 +398,8 @@ class PiCameraStreamer(BaseCamera):
self.camera.stop_recording(splitter_port=2)
logging.info("Recording stopped")
# Update state dictionary
self.state["record_active"] = False
# Update status dictionary
self.status["record_active"] = False
def stop_stream_recording(
self, splitter_port: int = 1, resolution: Tuple[int, int] = None
@ -467,7 +467,7 @@ class PiCameraStreamer(BaseCamera):
self.camera.resolution = resolution
# If the stream should be active
if self.state["stream_active"]:
if self.status["stream_active"]:
try:
# Start recording on stream port
self.camera.start_recording(
@ -633,7 +633,7 @@ class PiCameraStreamer(BaseCamera):
# Start stream recording (and set resolution)
self.start_stream_recording()
# Update state
# Update status
logging.debug("STREAM ACTIVE")
# While the iterator is not closed