mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-09 04:35:25 +03:00
Refactor activity api to send motion and audio data
This commit is contained in:
parent
a174d82eb9
commit
e88a0baccb
@ -5,12 +5,9 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import traceback
|
import traceback
|
||||||
from collections import defaultdict
|
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
|
|
||||||
import numpy as np
|
|
||||||
import pandas as pd
|
|
||||||
import requests
|
import requests
|
||||||
from flask import (
|
from flask import (
|
||||||
Blueprint,
|
Blueprint,
|
||||||
@ -632,102 +629,3 @@ def hourly_timeline():
|
|||||||
"hours": hours,
|
"hours": hours,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/<camera_name>/recording/hourly/activity")
|
|
||||||
def hourly_timeline_activity(camera_name: str):
|
|
||||||
"""Get hourly summary for timeline."""
|
|
||||||
if camera_name not in current_app.frigate_config.cameras:
|
|
||||||
return make_response(
|
|
||||||
jsonify({"success": False, "message": "Camera not found"}),
|
|
||||||
404,
|
|
||||||
)
|
|
||||||
|
|
||||||
before = request.args.get("before", type=float, default=datetime.now())
|
|
||||||
after = request.args.get(
|
|
||||||
"after", type=float, default=datetime.now() - timedelta(hours=1)
|
|
||||||
)
|
|
||||||
tz_name = request.args.get("timezone", default="utc", type=str)
|
|
||||||
|
|
||||||
_, minute_modifier, _ = get_tz_modifiers(tz_name)
|
|
||||||
minute_offset = int(minute_modifier.split(" ")[0])
|
|
||||||
|
|
||||||
all_recordings: list[Recordings] = (
|
|
||||||
Recordings.select(
|
|
||||||
Recordings.start_time,
|
|
||||||
Recordings.duration,
|
|
||||||
Recordings.objects,
|
|
||||||
Recordings.motion,
|
|
||||||
)
|
|
||||||
.where(Recordings.camera == camera_name)
|
|
||||||
.where(Recordings.motion > 0)
|
|
||||||
.where((Recordings.start_time > after) & (Recordings.end_time < before))
|
|
||||||
.order_by(Recordings.start_time.asc())
|
|
||||||
.iterator()
|
|
||||||
)
|
|
||||||
|
|
||||||
# data format is ex:
|
|
||||||
# {timestamp: [{ date: 1, count: 1, type: motion }]}] }}
|
|
||||||
hours: dict[int, list[dict[str, any]]] = defaultdict(list)
|
|
||||||
|
|
||||||
key = datetime.fromtimestamp(after).replace(second=0, microsecond=0) + timedelta(
|
|
||||||
minutes=minute_offset
|
|
||||||
)
|
|
||||||
check = (key + timedelta(hours=1)).timestamp()
|
|
||||||
|
|
||||||
# set initial start so data is representative of full hour
|
|
||||||
hours[int(key.timestamp())].append(
|
|
||||||
[
|
|
||||||
key.timestamp(),
|
|
||||||
0,
|
|
||||||
False,
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
for recording in all_recordings:
|
|
||||||
if recording.start_time > check:
|
|
||||||
hours[int(key.timestamp())].append(
|
|
||||||
[
|
|
||||||
(key + timedelta(minutes=59, seconds=59)).timestamp(),
|
|
||||||
0,
|
|
||||||
False,
|
|
||||||
]
|
|
||||||
)
|
|
||||||
key = key + timedelta(hours=1)
|
|
||||||
check = (key + timedelta(hours=1)).timestamp()
|
|
||||||
hours[int(key.timestamp())].append(
|
|
||||||
[
|
|
||||||
key.timestamp(),
|
|
||||||
0,
|
|
||||||
False,
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
data_type = recording.objects > 0
|
|
||||||
count = recording.motion + recording.objects
|
|
||||||
hours[int(key.timestamp())].append(
|
|
||||||
[
|
|
||||||
recording.start_time + (recording.duration / 2),
|
|
||||||
0 if count == 0 else np.log2(count),
|
|
||||||
data_type,
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
# resample data using pandas to get activity on minute to minute basis
|
|
||||||
for key, data in hours.items():
|
|
||||||
df = pd.DataFrame(data, columns=["date", "count", "hasObjects"])
|
|
||||||
|
|
||||||
# set date as datetime index
|
|
||||||
df["date"] = pd.to_datetime(df["date"], unit="s")
|
|
||||||
df.set_index(["date"], inplace=True)
|
|
||||||
|
|
||||||
# normalize data
|
|
||||||
df = df.resample("T").mean().fillna(0)
|
|
||||||
|
|
||||||
# change types for output
|
|
||||||
df.index = df.index.astype(int) // (10**9)
|
|
||||||
df["count"] = df["count"].astype(int)
|
|
||||||
df["hasObjects"] = df["hasObjects"].astype(bool)
|
|
||||||
hours[key] = df.reset_index().to_dict("records")
|
|
||||||
|
|
||||||
return jsonify(hours)
|
|
||||||
|
|||||||
@ -4,6 +4,8 @@ import logging
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
from flask import (
|
from flask import (
|
||||||
Blueprint,
|
Blueprint,
|
||||||
jsonify,
|
jsonify,
|
||||||
@ -12,7 +14,7 @@ from flask import (
|
|||||||
)
|
)
|
||||||
from peewee import Case, DoesNotExist, fn, operator
|
from peewee import Case, DoesNotExist, fn, operator
|
||||||
|
|
||||||
from frigate.models import ReviewSegment
|
from frigate.models import Recordings, ReviewSegment
|
||||||
from frigate.util.builtin import get_tz_modifiers
|
from frigate.util.builtin import get_tz_modifiers
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -258,3 +260,59 @@ def delete_reviews(ids: str):
|
|||||||
ReviewSegment.delete().where(ReviewSegment.id << list_of_ids).execute()
|
ReviewSegment.delete().where(ReviewSegment.id << list_of_ids).execute()
|
||||||
|
|
||||||
return make_response(jsonify({"success": True, "message": "Delete reviews"}), 200)
|
return make_response(jsonify({"success": True, "message": "Delete reviews"}), 200)
|
||||||
|
|
||||||
|
|
||||||
|
@ReviewBp.route("/review/activity")
|
||||||
|
def review_activity():
|
||||||
|
"""Get motion and audio activity."""
|
||||||
|
before = request.args.get("before", type=float, default=datetime.now().timestamp())
|
||||||
|
after = request.args.get(
|
||||||
|
"after", type=float, default=(datetime.now() - timedelta(hours=1)).timestamp()
|
||||||
|
)
|
||||||
|
|
||||||
|
# get scale in seconds
|
||||||
|
scale = request.args.get(
|
||||||
|
"scale", type=int, default=30
|
||||||
|
)
|
||||||
|
|
||||||
|
all_recordings: list[Recordings] = (
|
||||||
|
Recordings.select(
|
||||||
|
Recordings.start_time,
|
||||||
|
Recordings.duration,
|
||||||
|
Recordings.objects,
|
||||||
|
Recordings.motion,
|
||||||
|
Recordings.dBFS,
|
||||||
|
)
|
||||||
|
.where((Recordings.start_time > after) & (Recordings.end_time < before))
|
||||||
|
.order_by(Recordings.start_time.asc())
|
||||||
|
.iterator()
|
||||||
|
)
|
||||||
|
|
||||||
|
# format is: { timestamp: segment_start_ts, motion: [0-100], audio: [0 - -100] }
|
||||||
|
# periods where active objects / audio was detected will cause motion / audio to be scaled down
|
||||||
|
data: list[dict[str, float]] = []
|
||||||
|
|
||||||
|
for rec in all_recordings:
|
||||||
|
factor = 0.1 if rec.objects > 0 else 1.0
|
||||||
|
data.append({
|
||||||
|
"date": rec.start_time,
|
||||||
|
"motion": rec.motion * factor,
|
||||||
|
"audio": rec.dBFS * factor,
|
||||||
|
})
|
||||||
|
|
||||||
|
# resample data using pandas to get activity on scaled basis
|
||||||
|
df = pd.DataFrame(data, columns=["date", "motion", "audio"])
|
||||||
|
|
||||||
|
# set date as datetime index
|
||||||
|
df["date"] = pd.to_datetime(df["date"], unit="s")
|
||||||
|
df.set_index(["date"], inplace=True)
|
||||||
|
|
||||||
|
# normalize data
|
||||||
|
df = df.resample(f"{scale}S").mean().fillna(0.0)
|
||||||
|
df["motion"] = (df["motion"] - df["motion"].min()) / (df["motion"].max() - df["motion"].min()) * 100
|
||||||
|
df["audio"] = (df["audio"] - df["audio"].max()) / (df["audio"].min() - df["audio"].max()) * -100
|
||||||
|
|
||||||
|
# change types for output
|
||||||
|
df.index = df.index.astype(int) // (10**9)
|
||||||
|
normalized = df.reset_index().to_dict("records")
|
||||||
|
return jsonify(normalized)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user