Implement object lifecycle pane (#13550)

* Object lifecycle pane

* fix thumbnails and annotation offset math

* snapshot endpoint height and format, yaml types, bugfixes

* clean up for new type

* use get_image_from_recording in recordings snapshot api

* make height optional
This commit is contained in:
Josh Hawkins
2024-09-04 08:46:49 -05:00
committed by GitHub
parent e80322dab7
commit ddf9163c47
18 changed files with 1407 additions and 251 deletions

View File

@@ -179,14 +179,20 @@ def latest_frame(camera_name):
)
@MediaBp.route("/<camera_name>/recordings/<frame_time>/snapshot.png")
def get_snapshot_from_recording(camera_name: str, frame_time: str):
@MediaBp.route("/<camera_name>/recordings/<frame_time>/snapshot.<format>")
def get_snapshot_from_recording(camera_name: str, frame_time: str, format: str):
if camera_name not in current_app.frigate_config.cameras:
return make_response(
jsonify({"success": False, "message": "Camera not found"}),
404,
)
if format not in ["png", "jpg"]:
return make_response(
jsonify({"success": False, "message": "Invalid format"}),
400,
)
frame_time = float(frame_time)
recording_query = (
Recordings.select(
@@ -207,7 +213,13 @@ def get_snapshot_from_recording(camera_name: str, frame_time: str):
try:
recording: Recordings = recording_query.get()
time_in_segment = frame_time - recording.start_time
image_data = get_image_from_recording(recording.path, time_in_segment)
height = request.args.get("height", type=int)
codec = "png" if format == "png" else "mjpeg"
image_data = get_image_from_recording(
recording.path, time_in_segment, codec, height
)
if not image_data:
return make_response(
@@ -221,7 +233,7 @@ def get_snapshot_from_recording(camera_name: str, frame_time: str):
)
response = make_response(image_data)
response.headers["Content-Type"] = "image/png"
response.headers["Content-Type"] = f"image/{format}"
return response
except DoesNotExist:
return make_response(
@@ -263,7 +275,7 @@ def submit_recording_snapshot_to_plus(camera_name: str, frame_time: str):
try:
recording: Recordings = recording_query.get()
time_in_segment = frame_time - recording.start_time
image_data = get_image_from_recording(recording.path, time_in_segment)
image_data = get_image_from_recording(recording.path, time_in_segment, "png")
if not image_data:
return make_response(

View File

@@ -1,5 +1,6 @@
"""Utilities for builtin types manipulation."""
import ast
import copy
import datetime
import logging
@@ -210,10 +211,16 @@ def update_yaml_from_url(file_path, url):
if len(new_value_list) > 1:
update_yaml_file(file_path, key_path, new_value_list)
else:
value = str(new_value_list[0])
if value.isnumeric():
value = int(value)
value = new_value_list[0]
if "," in value:
# Skip conversion if we're a mask or zone string
update_yaml_file(file_path, key_path, value)
else:
try:
value = ast.literal_eval(value)
except (ValueError, SyntaxError):
pass
update_yaml_file(file_path, key_path, value)
update_yaml_file(file_path, key_path, value)

View File

@@ -765,7 +765,7 @@ def add_mask(mask: str, mask_img: np.ndarray):
def get_image_from_recording(
file_path: str, relative_frame_time: float
file_path: str, relative_frame_time: float, codec: str, height: Optional[int] = None
) -> Optional[any]:
"""retrieve a frame from given time in recording file."""
@@ -781,12 +781,16 @@ def get_image_from_recording(
"-frames:v",
"1",
"-c:v",
"png",
codec,
"-f",
"image2pipe",
"-",
]
if height is not None:
ffmpeg_cmd.insert(-3, "-vf")
ffmpeg_cmd.insert(-3, f"scale=-1:{height}")
process = sp.run(
ffmpeg_cmd,
capture_output=True,