add ability to parse and upload image from recording to frigate+

This commit is contained in:
Nicolas Mowen 2024-05-02 09:38:47 -06:00
parent b69c1828cb
commit fba0f66d79
2 changed files with 125 additions and 26 deletions

View File

@ -7,6 +7,7 @@ import os
import subprocess as sp
import time
from datetime import datetime, timedelta, timezone
from io import BytesIO
from urllib.parse import unquote
import cv2
@ -14,6 +15,7 @@ import numpy as np
import pytz
from flask import Blueprint, Response, current_app, jsonify, make_response, request
from peewee import DoesNotExist, fn
from PIL import Image
from tzlocal import get_localzone_name
from werkzeug.utils import secure_filename
@ -26,6 +28,7 @@ from frigate.const import (
)
from frigate.models import Event, Previews, Recordings, Regions, ReviewSegment
from frigate.util.builtin import get_tz_modifiers
from frigate.util.image import get_image_from_recording
logger = logging.getLogger(__name__)
@ -205,30 +208,20 @@ 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)
ffmpeg_cmd = [
"ffmpeg",
"-hide_banner",
"-loglevel",
"warning",
"-ss",
f"00:00:{time_in_segment}",
"-i",
recording.path,
"-frames:v",
"1",
"-c:v",
"png",
"-f",
"image2pipe",
"-",
]
if not image_data:
return make_response(
jsonify(
{
"success": False,
"message": f"Unable to parse frame at time {frame_time}",
}
),
404,
)
process = sp.run(
ffmpeg_cmd,
capture_output=True,
)
response = make_response(process.stdout)
response = make_response(image_data)
response.headers["Content-Type"] = "image/png"
return response
except DoesNotExist:
@ -243,6 +236,71 @@ def get_snapshot_from_recording(camera_name: str, frame_time: str):
)
@MediaBp.route("/<camera_name>/plus/<frame_time>")
def submit_recording_snapshot_to_plus(camera_name: str, frame_time: str):
if camera_name not in current_app.frigate_config.cameras:
return make_response(
jsonify({"success": False, "message": "Camera not found"}),
404,
)
frame_time = float(frame_time)
recording_query = (
Recordings.select(
Recordings.path,
Recordings.start_time,
)
.where(
(
(frame_time >= Recordings.start_time)
& (frame_time <= Recordings.end_time)
)
)
.where(Recordings.camera == camera_name)
.order_by(Recordings.start_time.desc())
.limit(1)
)
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)
if not image_data:
return make_response(
jsonify(
{
"success": False,
"message": f"Unable to parse frame at time {frame_time}",
}
),
404,
)
nd = cv2.imdecode(np.frombuffer(image_data, dtype=np.int8), cv2.IMREAD_COLOR)
current_app.plus_api.upload_image(nd, camera_name)
return make_response(
jsonify(
{
"success": True,
"message": "Successfully submitted image.",
}
),
200,
)
except DoesNotExist:
return make_response(
jsonify(
{
"success": False,
"message": "Recording not found at {}".format(frame_time),
}
),
404,
)
@MediaBp.route("/recordings/storage", methods=["GET"])
def get_recordings_storage_usage():
recording_stats = current_app.stats_emitter.get_latest_stats()["service"][

View File

@ -2,6 +2,7 @@
import datetime
import logging
import subprocess as sp
from abc import ABC, abstractmethod
from multiprocessing import shared_memory
from string import printable
@ -360,14 +361,17 @@ def yuv_crop_and_resize(frame, region, height=None):
# copy u2
yuv_cropped_frame[
size + uv_channel_y_offset : size + uv_channel_y_offset + uv_crop_height,
size // 2 + uv_channel_x_offset : size // 2
size // 2
+ uv_channel_x_offset : size // 2
+ uv_channel_x_offset
+ uv_crop_width,
] = frame[u2[1] : u2[3], u2[0] : u2[2]]
# copy v1
yuv_cropped_frame[
size + size // 4 + uv_channel_y_offset : size
size
+ size // 4
+ uv_channel_y_offset : size
+ size // 4
+ uv_channel_y_offset
+ uv_crop_height,
@ -376,11 +380,14 @@ def yuv_crop_and_resize(frame, region, height=None):
# copy v2
yuv_cropped_frame[
size + size // 4 + uv_channel_y_offset : size
size
+ size // 4
+ uv_channel_y_offset : size
+ size // 4
+ uv_channel_y_offset
+ uv_crop_height,
size // 2 + uv_channel_x_offset : size // 2
size // 2
+ uv_channel_x_offset : size // 2
+ uv_channel_x_offset
+ uv_crop_width,
] = frame[v2[1] : v2[3], v2[0] : v2[2]]
@ -746,3 +753,37 @@ def add_mask(mask: str, mask_img: np.ndarray):
]
)
cv2.fillPoly(mask_img, pts=[contour], color=(0))
def get_image_from_recording(
file_path: str, relative_frame_time: float
) -> Optional[any]:
"""retrieve a frame from given time in recording file."""
ffmpeg_cmd = [
"ffmpeg",
"-hide_banner",
"-loglevel",
"warning",
"-ss",
f"00:00:{relative_frame_time}",
"-i",
file_path,
"-frames:v",
"1",
"-c:v",
"png",
"-f",
"image2pipe",
"-",
]
process = sp.run(
ffmpeg_cmd,
capture_output=True,
)
if process.returncode == 0:
return process.stdout
else:
return None