mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-11 13:45:25 +03:00
add ability to parse and upload image from recording to frigate+
This commit is contained in:
parent
b69c1828cb
commit
fba0f66d79
@ -7,6 +7,7 @@ import os
|
|||||||
import subprocess as sp
|
import subprocess as sp
|
||||||
import time
|
import time
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
|
from io import BytesIO
|
||||||
from urllib.parse import unquote
|
from urllib.parse import unquote
|
||||||
|
|
||||||
import cv2
|
import cv2
|
||||||
@ -14,6 +15,7 @@ import numpy as np
|
|||||||
import pytz
|
import pytz
|
||||||
from flask import Blueprint, Response, current_app, jsonify, make_response, request
|
from flask import Blueprint, Response, current_app, jsonify, make_response, request
|
||||||
from peewee import DoesNotExist, fn
|
from peewee import DoesNotExist, fn
|
||||||
|
from PIL import Image
|
||||||
from tzlocal import get_localzone_name
|
from tzlocal import get_localzone_name
|
||||||
from werkzeug.utils import secure_filename
|
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.models import Event, Previews, Recordings, Regions, ReviewSegment
|
||||||
from frigate.util.builtin import get_tz_modifiers
|
from frigate.util.builtin import get_tz_modifiers
|
||||||
|
from frigate.util.image import get_image_from_recording
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -205,30 +208,20 @@ def get_snapshot_from_recording(camera_name: str, frame_time: str):
|
|||||||
try:
|
try:
|
||||||
recording: Recordings = recording_query.get()
|
recording: Recordings = recording_query.get()
|
||||||
time_in_segment = frame_time - recording.start_time
|
time_in_segment = frame_time - recording.start_time
|
||||||
|
image_data = get_image_from_recording(recording.path, time_in_segment)
|
||||||
|
|
||||||
ffmpeg_cmd = [
|
if not image_data:
|
||||||
"ffmpeg",
|
return make_response(
|
||||||
"-hide_banner",
|
jsonify(
|
||||||
"-loglevel",
|
{
|
||||||
"warning",
|
"success": False,
|
||||||
"-ss",
|
"message": f"Unable to parse frame at time {frame_time}",
|
||||||
f"00:00:{time_in_segment}",
|
}
|
||||||
"-i",
|
),
|
||||||
recording.path,
|
404,
|
||||||
"-frames:v",
|
|
||||||
"1",
|
|
||||||
"-c:v",
|
|
||||||
"png",
|
|
||||||
"-f",
|
|
||||||
"image2pipe",
|
|
||||||
"-",
|
|
||||||
]
|
|
||||||
|
|
||||||
process = sp.run(
|
|
||||||
ffmpeg_cmd,
|
|
||||||
capture_output=True,
|
|
||||||
)
|
)
|
||||||
response = make_response(process.stdout)
|
|
||||||
|
response = make_response(image_data)
|
||||||
response.headers["Content-Type"] = "image/png"
|
response.headers["Content-Type"] = "image/png"
|
||||||
return response
|
return response
|
||||||
except DoesNotExist:
|
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"])
|
@MediaBp.route("/recordings/storage", methods=["GET"])
|
||||||
def get_recordings_storage_usage():
|
def get_recordings_storage_usage():
|
||||||
recording_stats = current_app.stats_emitter.get_latest_stats()["service"][
|
recording_stats = current_app.stats_emitter.get_latest_stats()["service"][
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
|
import subprocess as sp
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from multiprocessing import shared_memory
|
from multiprocessing import shared_memory
|
||||||
from string import printable
|
from string import printable
|
||||||
@ -360,14 +361,17 @@ def yuv_crop_and_resize(frame, region, height=None):
|
|||||||
# copy u2
|
# copy u2
|
||||||
yuv_cropped_frame[
|
yuv_cropped_frame[
|
||||||
size + uv_channel_y_offset : size + uv_channel_y_offset + uv_crop_height,
|
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_channel_x_offset
|
||||||
+ uv_crop_width,
|
+ uv_crop_width,
|
||||||
] = frame[u2[1] : u2[3], u2[0] : u2[2]]
|
] = frame[u2[1] : u2[3], u2[0] : u2[2]]
|
||||||
|
|
||||||
# copy v1
|
# copy v1
|
||||||
yuv_cropped_frame[
|
yuv_cropped_frame[
|
||||||
size + size // 4 + uv_channel_y_offset : size
|
size
|
||||||
|
+ size // 4
|
||||||
|
+ uv_channel_y_offset : size
|
||||||
+ size // 4
|
+ size // 4
|
||||||
+ uv_channel_y_offset
|
+ uv_channel_y_offset
|
||||||
+ uv_crop_height,
|
+ uv_crop_height,
|
||||||
@ -376,11 +380,14 @@ def yuv_crop_and_resize(frame, region, height=None):
|
|||||||
|
|
||||||
# copy v2
|
# copy v2
|
||||||
yuv_cropped_frame[
|
yuv_cropped_frame[
|
||||||
size + size // 4 + uv_channel_y_offset : size
|
size
|
||||||
|
+ size // 4
|
||||||
|
+ uv_channel_y_offset : size
|
||||||
+ size // 4
|
+ size // 4
|
||||||
+ uv_channel_y_offset
|
+ uv_channel_y_offset
|
||||||
+ uv_crop_height,
|
+ uv_crop_height,
|
||||||
size // 2 + uv_channel_x_offset : size // 2
|
size // 2
|
||||||
|
+ uv_channel_x_offset : size // 2
|
||||||
+ uv_channel_x_offset
|
+ uv_channel_x_offset
|
||||||
+ uv_crop_width,
|
+ uv_crop_width,
|
||||||
] = frame[v2[1] : v2[3], v2[0] : v2[2]]
|
] = 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))
|
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
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user