mirror of
https://github.com/blakeblackshear/frigate.git
synced 2025-12-06 13:34:13 +03:00
Fix recordings summary for DST (#20784)
* make recordings summary endpoints DST aware * remove unused * clean up
This commit is contained in:
parent
85f7138361
commit
9e83888133
@ -46,7 +46,7 @@ from frigate.models import Event, Previews, Recordings, Regions, ReviewSegment
|
|||||||
from frigate.track.object_processing import TrackedObjectProcessor
|
from frigate.track.object_processing import TrackedObjectProcessor
|
||||||
from frigate.util.image import get_image_from_recording
|
from frigate.util.image import get_image_from_recording
|
||||||
from frigate.util.path import get_event_thumbnail_bytes
|
from frigate.util.path import get_event_thumbnail_bytes
|
||||||
from frigate.util.time import get_tz_modifiers
|
from frigate.util.time import get_dst_transitions
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -424,7 +424,6 @@ def all_recordings_summary(
|
|||||||
allowed_cameras: List[str] = Depends(get_allowed_cameras_for_filter),
|
allowed_cameras: List[str] = Depends(get_allowed_cameras_for_filter),
|
||||||
):
|
):
|
||||||
"""Returns true/false by day indicating if recordings exist"""
|
"""Returns true/false by day indicating if recordings exist"""
|
||||||
hour_modifier, minute_modifier, seconds_offset = get_tz_modifiers(params.timezone)
|
|
||||||
|
|
||||||
cameras = params.cameras
|
cameras = params.cameras
|
||||||
if cameras != "all":
|
if cameras != "all":
|
||||||
@ -432,41 +431,70 @@ def all_recordings_summary(
|
|||||||
filtered = requested.intersection(allowed_cameras)
|
filtered = requested.intersection(allowed_cameras)
|
||||||
if not filtered:
|
if not filtered:
|
||||||
return JSONResponse(content={})
|
return JSONResponse(content={})
|
||||||
cameras = ",".join(filtered)
|
camera_list = list(filtered)
|
||||||
else:
|
else:
|
||||||
cameras = allowed_cameras
|
camera_list = allowed_cameras
|
||||||
|
|
||||||
query = (
|
time_range_query = (
|
||||||
|
Recordings.select(
|
||||||
|
fn.MIN(Recordings.start_time).alias("min_time"),
|
||||||
|
fn.MAX(Recordings.start_time).alias("max_time"),
|
||||||
|
)
|
||||||
|
.where(Recordings.camera << camera_list)
|
||||||
|
.dicts()
|
||||||
|
.get()
|
||||||
|
)
|
||||||
|
|
||||||
|
min_time = time_range_query.get("min_time")
|
||||||
|
max_time = time_range_query.get("max_time")
|
||||||
|
|
||||||
|
if min_time is None or max_time is None:
|
||||||
|
return JSONResponse(content={})
|
||||||
|
|
||||||
|
dst_periods = get_dst_transitions(params.timezone, min_time, max_time)
|
||||||
|
|
||||||
|
days: dict[str, bool] = {}
|
||||||
|
|
||||||
|
for period_start, period_end, period_offset in dst_periods:
|
||||||
|
hours_offset = int(period_offset / 60 / 60)
|
||||||
|
minutes_offset = int(period_offset / 60 - hours_offset * 60)
|
||||||
|
period_hour_modifier = f"{hours_offset} hour"
|
||||||
|
period_minute_modifier = f"{minutes_offset} minute"
|
||||||
|
|
||||||
|
period_query = (
|
||||||
Recordings.select(
|
Recordings.select(
|
||||||
fn.strftime(
|
fn.strftime(
|
||||||
"%Y-%m-%d",
|
"%Y-%m-%d",
|
||||||
fn.datetime(
|
fn.datetime(
|
||||||
Recordings.start_time + seconds_offset,
|
Recordings.start_time,
|
||||||
"unixepoch",
|
"unixepoch",
|
||||||
hour_modifier,
|
period_hour_modifier,
|
||||||
minute_modifier,
|
period_minute_modifier,
|
||||||
),
|
),
|
||||||
).alias("day")
|
).alias("day")
|
||||||
)
|
)
|
||||||
|
.where(
|
||||||
|
(Recordings.camera << camera_list)
|
||||||
|
& (Recordings.end_time >= period_start)
|
||||||
|
& (Recordings.start_time <= period_end)
|
||||||
|
)
|
||||||
.group_by(
|
.group_by(
|
||||||
fn.strftime(
|
fn.strftime(
|
||||||
"%Y-%m-%d",
|
"%Y-%m-%d",
|
||||||
fn.datetime(
|
fn.datetime(
|
||||||
Recordings.start_time + seconds_offset,
|
Recordings.start_time,
|
||||||
"unixepoch",
|
"unixepoch",
|
||||||
hour_modifier,
|
period_hour_modifier,
|
||||||
minute_modifier,
|
period_minute_modifier,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.order_by(Recordings.start_time.desc())
|
.order_by(Recordings.start_time.desc())
|
||||||
|
.namedtuples()
|
||||||
)
|
)
|
||||||
|
|
||||||
if params.cameras != "all":
|
for g in period_query:
|
||||||
query = query.where(Recordings.camera << cameras.split(","))
|
days[g.day] = True
|
||||||
|
|
||||||
recording_days = query.namedtuples()
|
|
||||||
days = {day.day: True for day in recording_days}
|
|
||||||
|
|
||||||
return JSONResponse(content=days)
|
return JSONResponse(content=days)
|
||||||
|
|
||||||
@ -476,21 +504,54 @@ def all_recordings_summary(
|
|||||||
)
|
)
|
||||||
async def recordings_summary(camera_name: str, timezone: str = "utc"):
|
async def recordings_summary(camera_name: str, timezone: str = "utc"):
|
||||||
"""Returns hourly summary for recordings of given camera"""
|
"""Returns hourly summary for recordings of given camera"""
|
||||||
hour_modifier, minute_modifier, seconds_offset = get_tz_modifiers(timezone)
|
|
||||||
|
time_range_query = (
|
||||||
|
Recordings.select(
|
||||||
|
fn.MIN(Recordings.start_time).alias("min_time"),
|
||||||
|
fn.MAX(Recordings.start_time).alias("max_time"),
|
||||||
|
)
|
||||||
|
.where(Recordings.camera == camera_name)
|
||||||
|
.dicts()
|
||||||
|
.get()
|
||||||
|
)
|
||||||
|
|
||||||
|
min_time = time_range_query.get("min_time")
|
||||||
|
max_time = time_range_query.get("max_time")
|
||||||
|
|
||||||
|
days: dict[str, dict] = {}
|
||||||
|
|
||||||
|
if min_time is None or max_time is None:
|
||||||
|
return JSONResponse(content=list(days.values()))
|
||||||
|
|
||||||
|
dst_periods = get_dst_transitions(timezone, min_time, max_time)
|
||||||
|
|
||||||
|
for period_start, period_end, period_offset in dst_periods:
|
||||||
|
hours_offset = int(period_offset / 60 / 60)
|
||||||
|
minutes_offset = int(period_offset / 60 - hours_offset * 60)
|
||||||
|
period_hour_modifier = f"{hours_offset} hour"
|
||||||
|
period_minute_modifier = f"{minutes_offset} minute"
|
||||||
|
|
||||||
recording_groups = (
|
recording_groups = (
|
||||||
Recordings.select(
|
Recordings.select(
|
||||||
fn.strftime(
|
fn.strftime(
|
||||||
"%Y-%m-%d %H",
|
"%Y-%m-%d %H",
|
||||||
fn.datetime(
|
fn.datetime(
|
||||||
Recordings.start_time, "unixepoch", hour_modifier, minute_modifier
|
Recordings.start_time,
|
||||||
|
"unixepoch",
|
||||||
|
period_hour_modifier,
|
||||||
|
period_minute_modifier,
|
||||||
),
|
),
|
||||||
).alias("hour"),
|
).alias("hour"),
|
||||||
fn.SUM(Recordings.duration).alias("duration"),
|
fn.SUM(Recordings.duration).alias("duration"),
|
||||||
fn.SUM(Recordings.motion).alias("motion"),
|
fn.SUM(Recordings.motion).alias("motion"),
|
||||||
fn.SUM(Recordings.objects).alias("objects"),
|
fn.SUM(Recordings.objects).alias("objects"),
|
||||||
)
|
)
|
||||||
.where(Recordings.camera == camera_name)
|
.where(
|
||||||
.group_by((Recordings.start_time + seconds_offset).cast("int") / 3600)
|
(Recordings.camera == camera_name)
|
||||||
|
& (Recordings.end_time >= period_start)
|
||||||
|
& (Recordings.start_time <= period_end)
|
||||||
|
)
|
||||||
|
.group_by((Recordings.start_time + period_offset).cast("int") / 3600)
|
||||||
.order_by(Recordings.start_time.desc())
|
.order_by(Recordings.start_time.desc())
|
||||||
.namedtuples()
|
.namedtuples()
|
||||||
)
|
)
|
||||||
@ -500,20 +561,24 @@ async def recordings_summary(camera_name: str, timezone: str = "utc"):
|
|||||||
fn.strftime(
|
fn.strftime(
|
||||||
"%Y-%m-%d %H",
|
"%Y-%m-%d %H",
|
||||||
fn.datetime(
|
fn.datetime(
|
||||||
Event.start_time, "unixepoch", hour_modifier, minute_modifier
|
Event.start_time,
|
||||||
|
"unixepoch",
|
||||||
|
period_hour_modifier,
|
||||||
|
period_minute_modifier,
|
||||||
),
|
),
|
||||||
).alias("hour"),
|
).alias("hour"),
|
||||||
fn.COUNT(Event.id).alias("count"),
|
fn.COUNT(Event.id).alias("count"),
|
||||||
)
|
)
|
||||||
.where(Event.camera == camera_name, Event.has_clip)
|
.where(Event.camera == camera_name, Event.has_clip)
|
||||||
.group_by((Event.start_time + seconds_offset).cast("int") / 3600)
|
.where(
|
||||||
|
(Event.start_time >= period_start) & (Event.start_time <= period_end)
|
||||||
|
)
|
||||||
|
.group_by((Event.start_time + period_offset).cast("int") / 3600)
|
||||||
.namedtuples()
|
.namedtuples()
|
||||||
)
|
)
|
||||||
|
|
||||||
event_map = {g.hour: g.count for g in event_groups}
|
event_map = {g.hour: g.count for g in event_groups}
|
||||||
|
|
||||||
days = {}
|
|
||||||
|
|
||||||
for recording_group in recording_groups:
|
for recording_group in recording_groups:
|
||||||
parts = recording_group.hour.split()
|
parts = recording_group.hour.split()
|
||||||
hour = parts[1]
|
hour = parts[1]
|
||||||
@ -526,11 +591,16 @@ async def recordings_summary(camera_name: str, timezone: str = "utc"):
|
|||||||
"objects": recording_group.objects,
|
"objects": recording_group.objects,
|
||||||
"duration": round(recording_group.duration),
|
"duration": round(recording_group.duration),
|
||||||
}
|
}
|
||||||
if day not in days:
|
if day in days:
|
||||||
days[day] = {"events": events_count, "hours": [hour_data], "day": day}
|
# merge counts if already present (edge-case at DST boundary)
|
||||||
else:
|
days[day]["events"] += events_count or 0
|
||||||
days[day]["events"] += events_count
|
|
||||||
days[day]["hours"].append(hour_data)
|
days[day]["hours"].append(hour_data)
|
||||||
|
else:
|
||||||
|
days[day] = {
|
||||||
|
"events": events_count or 0,
|
||||||
|
"hours": [hour_data],
|
||||||
|
"day": day,
|
||||||
|
}
|
||||||
|
|
||||||
return JSONResponse(content=list(days.values()))
|
return JSONResponse(content=list(days.values()))
|
||||||
|
|
||||||
|
|||||||
@ -36,7 +36,7 @@ from frigate.config import FrigateConfig
|
|||||||
from frigate.embeddings import EmbeddingsContext
|
from frigate.embeddings import EmbeddingsContext
|
||||||
from frigate.models import Recordings, ReviewSegment, UserReviewStatus
|
from frigate.models import Recordings, ReviewSegment, UserReviewStatus
|
||||||
from frigate.review.types import SeverityEnum
|
from frigate.review.types import SeverityEnum
|
||||||
from frigate.util.time import get_dst_transitions, get_tz_modifiers
|
from frigate.util.time import get_dst_transitions
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -197,7 +197,6 @@ async def review_summary(
|
|||||||
|
|
||||||
user_id = current_user["username"]
|
user_id = current_user["username"]
|
||||||
|
|
||||||
hour_modifier, minute_modifier, seconds_offset = get_tz_modifiers(params.timezone)
|
|
||||||
day_ago = (datetime.datetime.now() - datetime.timedelta(hours=24)).timestamp()
|
day_ago = (datetime.datetime.now() - datetime.timedelta(hours=24)).timestamp()
|
||||||
|
|
||||||
cameras = params.cameras
|
cameras = params.cameras
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user