Updated Documentation for the Review endpoints (#14401)

* Updated documentation for the review endpoint

* Updated documentation for the review/summary endpoint

* Updated documentation for the review/summary endpoint

* Documentation for the review activity audio and motion endpoints

* Added responses for more review.py endpoints

* Added responses for more review.py endpoints

* Fixed review.py responses and proper path parameter names

* Added body model for /reviews/viewed and /reviews/delete

* Updated OpenAPI specification for the review controller endpoints

* Run ruff format frigate

* Drop significant_motion

* Updated frigate-api.yaml

* Deleted total_motion

* Combine 2 models into generic
This commit is contained in:
Rui Alves 2024-10-23 14:35:49 +01:00 committed by GitHub
parent 8bc145472a
commit fa81d87dc0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 404 additions and 326 deletions

View File

@ -172,76 +172,65 @@ paths:
in: query in: query
required: false required: false
schema: schema:
anyOf: type: string
- type: string
- type: 'null'
default: all default: all
title: Cameras title: Cameras
- name: labels - name: labels
in: query in: query
required: false required: false
schema: schema:
anyOf: type: string
- type: string
- type: 'null'
default: all default: all
title: Labels title: Labels
- name: zones - name: zones
in: query in: query
required: false required: false
schema: schema:
anyOf: type: string
- type: string
- type: 'null'
default: all default: all
title: Zones title: Zones
- name: reviewed - name: reviewed
in: query in: query
required: false required: false
schema: schema:
anyOf: type: integer
- type: integer
- type: 'null'
default: 0 default: 0
title: Reviewed title: Reviewed
- name: limit - name: limit
in: query in: query
required: false required: false
schema: schema:
anyOf: type: integer
- type: integer
- type: 'null'
title: Limit title: Limit
- name: severity - name: severity
in: query in: query
required: false required: false
schema: schema:
anyOf: allOf:
- type: string - $ref: '#/components/schemas/SeverityEnum'
- type: 'null'
title: Severity title: Severity
- name: before - name: before
in: query in: query
required: false required: false
schema: schema:
anyOf: type: number
- type: number
- type: 'null'
title: Before title: Before
- name: after - name: after
in: query in: query
required: false required: false
schema: schema:
anyOf: type: number
- type: number
- type: 'null'
title: After title: After
responses: responses:
'200': '200':
description: Successful Response description: Successful Response
content: content:
application/json: application/json:
schema: { } schema:
type: array
items:
$ref: '#/components/schemas/ReviewSegmentResponse'
title: Response Review Review Get
'422': '422':
description: Validation Error description: Validation Error
content: content:
@ -259,36 +248,28 @@ paths:
in: query in: query
required: false required: false
schema: schema:
anyOf: type: string
- type: string
- type: 'null'
default: all default: all
title: Cameras title: Cameras
- name: labels - name: labels
in: query in: query
required: false required: false
schema: schema:
anyOf: type: string
- type: string
- type: 'null'
default: all default: all
title: Labels title: Labels
- name: zones - name: zones
in: query in: query
required: false required: false
schema: schema:
anyOf: type: string
- type: string
- type: 'null'
default: all default: all
title: Zones title: Zones
- name: timezone - name: timezone
in: query in: query
required: false required: false
schema: schema:
anyOf: type: string
- type: string
- type: 'null'
default: utc default: utc
title: Timezone title: Timezone
responses: responses:
@ -296,7 +277,8 @@ paths:
description: Successful Response description: Successful Response
content: content:
application/json: application/json:
schema: { } schema:
$ref: '#/components/schemas/ReviewSummaryResponse'
'422': '422':
description: Validation Error description: Validation Error
content: content:
@ -310,17 +292,18 @@ paths:
summary: Set Multiple Reviewed summary: Set Multiple Reviewed
operationId: set_multiple_reviewed_reviews_viewed_post operationId: set_multiple_reviewed_reviews_viewed_post
requestBody: requestBody:
required: true
content: content:
application/json: application/json:
schema: schema:
type: object $ref: '#/components/schemas/ReviewSetMultipleReviewedBody'
title: Body
responses: responses:
'200': '200':
description: Successful Response description: Successful Response
content: content:
application/json: application/json:
schema: { } schema:
$ref: '#/components/schemas/GenericResponse'
'422': '422':
description: Validation Error description: Validation Error
content: content:
@ -334,17 +317,18 @@ paths:
summary: Delete Reviews summary: Delete Reviews
operationId: delete_reviews_reviews_delete_post operationId: delete_reviews_reviews_delete_post
requestBody: requestBody:
required: true
content: content:
application/json: application/json:
schema: schema:
type: object $ref: '#/components/schemas/ReviewDeleteMultipleReviewsBody'
title: Body
responses: responses:
'200': '200':
description: Successful Response description: Successful Response
content: content:
application/json: application/json:
schema: { } schema:
$ref: '#/components/schemas/GenericResponse'
'422': '422':
description: Validation Error description: Validation Error
content: content:
@ -363,96 +347,38 @@ paths:
in: query in: query
required: false required: false
schema: schema:
anyOf: type: string
- type: string
- type: 'null'
default: all default: all
title: Cameras title: Cameras
- name: before - name: before
in: query in: query
required: false required: false
schema: schema:
anyOf: type: number
- type: number
- type: 'null'
title: Before title: Before
- name: after - name: after
in: query in: query
required: false required: false
schema: schema:
anyOf: type: number
- type: number
- type: 'null'
title: After title: After
- name: scale - name: scale
in: query in: query
required: false required: false
schema: schema:
anyOf: type: integer
- type: integer
- type: 'null'
default: 30 default: 30
title: Scale title: Scale
responses: responses:
'200': '200':
description: Successful Response description: Successful Response
content:
application/json:
schema: { }
'422':
description: Validation Error
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/HTTPValidationError' type: array
/review/activity/audio: items:
get: $ref: '#/components/schemas/ReviewActivityMotionResponse'
tags: title: Response Motion Activity Review Activity Motion Get
- Review
summary: Audio Activity
description: Get motion and audio activity.
operationId: audio_activity_review_activity_audio_get
parameters:
- name: cameras
in: query
required: false
schema:
anyOf:
- type: string
- type: 'null'
default: all
title: Cameras
- name: before
in: query
required: false
schema:
anyOf:
- type: number
- type: 'null'
title: Before
- name: after
in: query
required: false
schema:
anyOf:
- type: number
- type: 'null'
title: After
- name: scale
in: query
required: false
schema:
anyOf:
- type: integer
- type: 'null'
default: 30
title: Scale
responses:
'200':
description: Successful Response
content:
application/json:
schema: { }
'422': '422':
description: Validation Error description: Validation Error
content: content:
@ -477,57 +403,60 @@ paths:
description: Successful Response description: Successful Response
content: content:
application/json: application/json:
schema: { } schema:
$ref: '#/components/schemas/ReviewSegmentResponse'
'422': '422':
description: Validation Error description: Validation Error
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/HTTPValidationError' $ref: '#/components/schemas/HTTPValidationError'
/review/{event_id}: /review/{review_id}:
get: get:
tags: tags:
- Review - Review
summary: Get Review summary: Get Review
operationId: get_review_review__event_id__get operationId: get_review_review__review_id__get
parameters: parameters:
- name: event_id - name: review_id
in: path in: path
required: true required: true
schema: schema:
type: string type: string
title: Event Id title: Review Id
responses: responses:
'200': '200':
description: Successful Response description: Successful Response
content: content:
application/json: application/json:
schema: { } schema:
$ref: '#/components/schemas/ReviewSegmentResponse'
'422': '422':
description: Validation Error description: Validation Error
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/HTTPValidationError' $ref: '#/components/schemas/HTTPValidationError'
/review/{event_id}/viewed: /review/{review_id}/viewed:
delete: delete:
tags: tags:
- Review - Review
summary: Set Not Reviewed summary: Set Not Reviewed
operationId: set_not_reviewed_review__event_id__viewed_delete operationId: set_not_reviewed_review__review_id__viewed_delete
parameters: parameters:
- name: event_id - name: review_id
in: path in: path
required: true required: true
schema: schema:
type: string type: string
title: Event Id title: Review Id
responses: responses:
'200': '200':
description: Successful Response description: Successful Response
content: content:
application/json: application/json:
schema: { } schema:
$ref: '#/components/schemas/GenericResponse'
'422': '422':
description: Validation Error description: Validation Error
content: content:
@ -763,13 +692,25 @@ paths:
content: content:
application/json: application/json:
schema: { } schema: { }
/nvinfo:
get:
tags:
- App
summary: Nvinfo
operationId: nvinfo_nvinfo_get
responses:
'200':
description: Successful Response
content:
application/json:
schema: { }
/logs/{service}: /logs/{service}:
get: get:
tags: tags:
- App - App
- Logs - Logs
summary: Logs summary: Logs
description: Get logs for the requested service (frigate/nginx/go2rtc/chroma) description: Get logs for the requested service (frigate/nginx/go2rtc)
operationId: logs_logs__service__get operationId: logs_logs__service__get
parameters: parameters:
- name: service - name: service
@ -781,7 +722,6 @@ paths:
- frigate - frigate
- nginx - nginx
- go2rtc - go2rtc
- chroma
title: Service title: Service
- name: download - name: download
in: query in: query
@ -1042,7 +982,8 @@ paths:
- Preview - Preview
summary: Preview Hour summary: Preview Hour
description: Get all mp4 previews relevant for time period given the timezone description: Get all mp4 previews relevant for time period given the timezone
operationId: preview_hour_preview__year_month___day___hour___camera_name___tz_name__get operationId: >-
preview_hour_preview__year_month___day___hour___camera_name___tz_name__get
parameters: parameters:
- name: year_month - name: year_month
in: path in: path
@ -1092,7 +1033,8 @@ paths:
- Preview - Preview
summary: Get Preview Frames From Cache summary: Get Preview Frames From Cache
description: Get list of cached preview frames description: Get list of cached preview frames
operationId: get_preview_frames_from_cache_preview__camera_name__start__start_ts__end__end_ts__frames_get operationId: >-
get_preview_frames_from_cache_preview__camera_name__start__start_ts__end__end_ts__frames_get
parameters: parameters:
- name: camera_name - name: camera_name
in: path in: path
@ -1177,7 +1119,8 @@ paths:
tags: tags:
- Export - Export
summary: Export Recording summary: Export Recording
operationId: export_recording_export__camera_name__start__start_time__end__end_time__post operationId: >-
export_recording_export__camera_name__start__start_time__end__end_time__post
parameters: parameters:
- name: camera_name - name: camera_name
in: path in: path
@ -1656,6 +1599,30 @@ paths:
- type: 'null' - type: 'null'
default: utc default: utc
title: Timezone title: Timezone
- name: min_score
in: query
required: false
schema:
anyOf:
- type: number
- type: 'null'
title: Min Score
- name: max_score
in: query
required: false
schema:
anyOf:
- type: number
- type: 'null'
title: Max Score
- name: sort
in: query
required: false
schema:
anyOf:
- type: string
- type: 'null'
title: Sort
responses: responses:
'200': '200':
description: Successful Response description: Successful Response
@ -1942,6 +1909,15 @@ paths:
schema: schema:
type: string type: string
title: Event Id title: Event Id
- name: source
in: query
required: false
schema:
anyOf:
- $ref: '#/components/schemas/RegenerateDescriptionEnum'
- type: 'null'
default: thumbnails
title: Source
responses: responses:
'200': '200':
description: Successful Response description: Successful Response
@ -2029,12 +2005,12 @@ paths:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/HTTPValidationError' $ref: '#/components/schemas/HTTPValidationError'
'{camera_name}': /{camera_name}:
get: get:
tags: tags:
- Media - Media
summary: Mjpeg Feed summary: Mjpeg Feed
operationId: mjpeg_feed_camera_name__get operationId: mjpeg_feed__camera_name__get
parameters: parameters:
- name: camera_name - name: camera_name
in: path in: path
@ -2241,7 +2217,8 @@ paths:
tags: tags:
- Media - Media
summary: Get Snapshot From Recording summary: Get Snapshot From Recording
operationId: get_snapshot_from_recording__camera_name__recordings__frame_time__snapshot__format__get operationId: >-
get_snapshot_from_recording__camera_name__recordings__frame_time__snapshot__format__get
parameters: parameters:
- name: camera_name - name: camera_name
in: path in: path
@ -2363,7 +2340,9 @@ paths:
tags: tags:
- Media - Media
summary: Recordings summary: Recordings
description: Return specific camera recordings between the given 'after'/'end' times. If not provided the last hour will be used description: >-
Return specific camera recordings between the given 'after'/'end' times.
If not provided the last hour will be used
operationId: recordings__camera_name__recordings_get operationId: recordings__camera_name__recordings_get
parameters: parameters:
- name: camera_name - name: camera_name
@ -2377,14 +2356,14 @@ paths:
required: false required: false
schema: schema:
type: number type: number
default: 1727542549.303557 default: 1729274204.653048
title: After title: After
- name: before - name: before
in: query in: query
required: false required: false
schema: schema:
type: number type: number
default: 1727546149.303926 default: 1729277804.653095
title: Before title: Before
responses: responses:
'200': '200':
@ -2423,13 +2402,6 @@ paths:
schema: schema:
type: number type: number
title: End Ts title: End Ts
- name: download
in: query
required: false
schema:
type: boolean
default: false
title: Download
responses: responses:
'200': '200':
description: Successful Response description: Successful Response
@ -2800,13 +2772,6 @@ paths:
schema: schema:
type: string type: string
title: Event Id title: Event Id
- name: download
in: query
required: false
schema:
type: boolean
default: false
title: Download
responses: responses:
'200': '200':
description: Successful Response description: Successful Response
@ -3121,7 +3086,9 @@ paths:
tags: tags:
- Media - Media
summary: Label Snapshot summary: Label Snapshot
description: Returns the snapshot image from the latest event for the given camera and label combo description: >-
Returns the snapshot image from the latest event for the given camera
and label combo
operationId: label_snapshot__camera_name___label__snapshot_jpg_get operationId: label_snapshot__camera_name___label__snapshot_jpg_get
parameters: parameters:
- name: camera_name - name: camera_name
@ -3193,6 +3160,32 @@ components:
required: required:
- password - password
title: AppPutPasswordBody title: AppPutPasswordBody
DayReview:
properties:
day:
type: string
format: date-time
title: Day
reviewed_alert:
type: integer
title: Reviewed Alert
reviewed_detection:
type: integer
title: Reviewed Detection
total_alert:
type: integer
title: Total Alert
total_detection:
type: integer
title: Total Detection
type: object
required:
- day
- reviewed_alert
- reviewed_detection
- total_alert
- total_detection
title: DayReview
EventsCreateBody: EventsCreateBody:
properties: properties:
source_type: source_type:
@ -3237,7 +3230,6 @@ components:
description: description:
anyOf: anyOf:
- type: string - type: string
minLength: 1
- type: 'null' - type: 'null'
title: The description of the event title: The description of the event
type: object type: object
@ -3278,6 +3270,19 @@ components:
- jpg - jpg
- jpeg - jpeg
title: Extension title: Extension
GenericResponse:
properties:
success:
type: boolean
title: Success
message:
type: string
title: Message
type: object
required:
- success
- message
title: GenericResponse
HTTPValidationError: HTTPValidationError:
properties: properties:
detail: detail:
@ -3287,6 +3292,133 @@ components:
title: Detail title: Detail
type: object type: object
title: HTTPValidationError title: HTTPValidationError
Last24HoursReview:
properties:
reviewed_alert:
type: integer
title: Reviewed Alert
reviewed_detection:
type: integer
title: Reviewed Detection
total_alert:
type: integer
title: Total Alert
total_detection:
type: integer
title: Total Detection
type: object
required:
- reviewed_alert
- reviewed_detection
- total_alert
- total_detection
title: Last24HoursReview
RegenerateDescriptionEnum:
type: string
enum:
- thumbnails
- snapshot
title: RegenerateDescriptionEnum
ReviewActivityMotionResponse:
properties:
start_time:
type: integer
title: Start Time
motion:
type: number
title: Motion
camera:
type: string
title: Camera
type: object
required:
- start_time
- motion
- camera
title: ReviewActivityMotionResponse
ReviewDeleteMultipleReviewsBody:
properties:
ids:
items:
type: string
minLength: 1
type: array
minItems: 1
title: Ids
type: object
required:
- ids
title: ReviewDeleteMultipleReviewsBody
ReviewSegmentResponse:
properties:
id:
type: string
title: Id
camera:
type: string
title: Camera
start_time:
type: string
format: date-time
title: Start Time
end_time:
type: string
format: date-time
title: End Time
has_been_reviewed:
type: boolean
title: Has Been Reviewed
severity:
$ref: '#/components/schemas/SeverityEnum'
thumb_path:
type: string
title: Thumb Path
data:
title: Data
type: object
required:
- id
- camera
- start_time
- end_time
- has_been_reviewed
- severity
- thumb_path
- data
title: ReviewSegmentResponse
ReviewSetMultipleReviewedBody:
properties:
ids:
items:
type: string
minLength: 1
type: array
minItems: 1
title: Ids
type: object
required:
- ids
title: ReviewSetMultipleReviewedBody
ReviewSummaryResponse:
properties:
last24Hours:
$ref: '#/components/schemas/Last24HoursReview'
root:
additionalProperties:
$ref: '#/components/schemas/DayReview'
type: object
title: Root
type: object
required:
- last24Hours
- root
title: ReviewSummaryResponse
SeverityEnum:
type: string
enum:
- alert
- detection
title: SeverityEnum
SubmitPlusBody: SubmitPlusBody:
properties: properties:
include_annotation: include_annotation:

View File

@ -0,0 +1,6 @@
from pydantic import BaseModel
class GenericResponse(BaseModel):
success: bool
message: str

View File

@ -0,0 +1,6 @@
from pydantic import BaseModel, conlist, constr
class ReviewModifyMultipleBody(BaseModel):
# List of string with at least one element and each element with at least one char
ids: conlist(constr(min_length=1), min_length=1)

View File

@ -1,28 +1,31 @@
from typing import Optional from typing import Union
from pydantic import BaseModel from pydantic import BaseModel
from pydantic.json_schema import SkipJsonSchema
from frigate.review.maintainer import SeverityEnum
class ReviewQueryParams(BaseModel): class ReviewQueryParams(BaseModel):
cameras: Optional[str] = "all" cameras: str = "all"
labels: Optional[str] = "all" labels: str = "all"
zones: Optional[str] = "all" zones: str = "all"
reviewed: Optional[int] = 0 reviewed: int = 0
limit: Optional[int] = None limit: Union[int, SkipJsonSchema[None]] = None
severity: Optional[str] = None severity: Union[SeverityEnum, SkipJsonSchema[None]] = None
before: Optional[float] = None before: Union[float, SkipJsonSchema[None]] = None
after: Optional[float] = None after: Union[float, SkipJsonSchema[None]] = None
class ReviewSummaryQueryParams(BaseModel): class ReviewSummaryQueryParams(BaseModel):
cameras: Optional[str] = "all" cameras: str = "all"
labels: Optional[str] = "all" labels: str = "all"
zones: Optional[str] = "all" zones: str = "all"
timezone: Optional[str] = "utc" timezone: str = "utc"
class ReviewActivityMotionQueryParams(BaseModel): class ReviewActivityMotionQueryParams(BaseModel):
cameras: Optional[str] = "all" cameras: str = "all"
before: Optional[float] = None before: Union[float, SkipJsonSchema[None]] = None
after: Optional[float] = None after: Union[float, SkipJsonSchema[None]] = None
scale: Optional[int] = 30 scale: int = 30

View File

@ -0,0 +1,43 @@
from datetime import datetime
from typing import Dict
from pydantic import BaseModel, Json
from frigate.review.maintainer import SeverityEnum
class ReviewSegmentResponse(BaseModel):
id: str
camera: str
start_time: datetime
end_time: datetime
has_been_reviewed: bool
severity: SeverityEnum
thumb_path: str
data: Json
class Last24HoursReview(BaseModel):
reviewed_alert: int
reviewed_detection: int
total_alert: int
total_detection: int
class DayReview(BaseModel):
day: datetime
reviewed_alert: int
reviewed_detection: int
total_alert: int
total_detection: int
class ReviewSummaryResponse(BaseModel):
last24Hours: Last24HoursReview
root: Dict[str, DayReview]
class ReviewActivityMotionResponse(BaseModel):
start_time: int
motion: float
camera: str

View File

@ -82,6 +82,10 @@ def create_fastapi_app(
database.close() database.close()
return response return response
@app.on_event("startup")
async def startup():
logger.info("FastAPI started")
# Rate limiter (used for login endpoint) # Rate limiter (used for login endpoint)
auth.rateLimiter.set_limit(frigate_config.auth.failed_login_rate_limit or "") auth.rateLimiter.set_limit(frigate_config.auth.failed_login_rate_limit or "")
app.state.limiter = limiter app.state.limiter = limiter

View File

@ -12,11 +12,18 @@ from fastapi.responses import JSONResponse
from peewee import Case, DoesNotExist, fn, operator from peewee import Case, DoesNotExist, fn, operator
from playhouse.shortcuts import model_to_dict from playhouse.shortcuts import model_to_dict
from frigate.api.defs.generic_response import GenericResponse
from frigate.api.defs.review_body import ReviewModifyMultipleBody
from frigate.api.defs.review_query_parameters import ( from frigate.api.defs.review_query_parameters import (
ReviewActivityMotionQueryParams, ReviewActivityMotionQueryParams,
ReviewQueryParams, ReviewQueryParams,
ReviewSummaryQueryParams, ReviewSummaryQueryParams,
) )
from frigate.api.defs.review_responses import (
ReviewActivityMotionResponse,
ReviewSegmentResponse,
ReviewSummaryResponse,
)
from frigate.api.defs.tags import Tags from frigate.api.defs.tags import Tags
from frigate.models import Recordings, ReviewSegment from frigate.models import Recordings, ReviewSegment
from frigate.util.builtin import get_tz_modifiers from frigate.util.builtin import get_tz_modifiers
@ -26,7 +33,7 @@ logger = logging.getLogger(__name__)
router = APIRouter(tags=[Tags.review]) router = APIRouter(tags=[Tags.review])
@router.get("/review") @router.get("/review", response_model=list[ReviewSegmentResponse])
def review(params: ReviewQueryParams = Depends()): def review(params: ReviewQueryParams = Depends()):
cameras = params.cameras cameras = params.cameras
labels = params.labels labels = params.labels
@ -102,7 +109,7 @@ def review(params: ReviewQueryParams = Depends()):
return JSONResponse(content=[r for r in review]) return JSONResponse(content=[r for r in review])
@router.get("/review/summary") @router.get("/review/summary", response_model=ReviewSummaryResponse)
def review_summary(params: ReviewSummaryQueryParams = Depends()): def review_summary(params: ReviewSummaryQueryParams = Depends()):
hour_modifier, minute_modifier, seconds_offset = get_tz_modifiers(params.timezone) 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()
@ -173,18 +180,6 @@ def review_summary(params: ReviewSummaryQueryParams = Depends()):
0, 0,
) )
).alias("reviewed_detection"), ).alias("reviewed_detection"),
fn.SUM(
Case(
None,
[
(
(ReviewSegment.severity == "significant_motion"),
ReviewSegment.has_been_reviewed,
)
],
0,
)
).alias("reviewed_motion"),
fn.SUM( fn.SUM(
Case( Case(
None, None,
@ -209,18 +204,6 @@ def review_summary(params: ReviewSummaryQueryParams = Depends()):
0, 0,
) )
).alias("total_detection"), ).alias("total_detection"),
fn.SUM(
Case(
None,
[
(
(ReviewSegment.severity == "significant_motion"),
1,
)
],
0,
)
).alias("total_motion"),
) )
.where(reduce(operator.and_, clauses)) .where(reduce(operator.and_, clauses))
.dicts() .dicts()
@ -282,18 +265,6 @@ def review_summary(params: ReviewSummaryQueryParams = Depends()):
0, 0,
) )
).alias("reviewed_detection"), ).alias("reviewed_detection"),
fn.SUM(
Case(
None,
[
(
(ReviewSegment.severity == "significant_motion"),
ReviewSegment.has_been_reviewed,
)
],
0,
)
).alias("reviewed_motion"),
fn.SUM( fn.SUM(
Case( Case(
None, None,
@ -318,18 +289,6 @@ def review_summary(params: ReviewSummaryQueryParams = Depends()):
0, 0,
) )
).alias("total_detection"), ).alias("total_detection"),
fn.SUM(
Case(
None,
[
(
(ReviewSegment.severity == "significant_motion"),
1,
)
],
0,
)
).alias("total_motion"),
) )
.where(reduce(operator.and_, clauses)) .where(reduce(operator.and_, clauses))
.group_by( .group_by(
@ -348,19 +307,10 @@ def review_summary(params: ReviewSummaryQueryParams = Depends()):
return JSONResponse(content=data) return JSONResponse(content=data)
@router.post("/reviews/viewed") @router.post("/reviews/viewed", response_model=GenericResponse)
def set_multiple_reviewed(body: dict = None): def set_multiple_reviewed(body: ReviewModifyMultipleBody):
json: dict[str, any] = body or {}
list_of_ids = json.get("ids", "")
if not list_of_ids or len(list_of_ids) == 0:
return JSONResponse(
context=({"success": False, "message": "Not a valid list of ids"}),
status_code=404,
)
ReviewSegment.update(has_been_reviewed=True).where( ReviewSegment.update(has_been_reviewed=True).where(
ReviewSegment.id << list_of_ids ReviewSegment.id << body.ids
).execute() ).execute()
return JSONResponse( return JSONResponse(
@ -369,17 +319,9 @@ def set_multiple_reviewed(body: dict = None):
) )
@router.post("/reviews/delete") @router.post("/reviews/delete", response_model=GenericResponse)
def delete_reviews(body: dict = None): def delete_reviews(body: ReviewModifyMultipleBody):
json: dict[str, any] = body or {} list_of_ids = body.ids
list_of_ids = json.get("ids", "")
if not list_of_ids or len(list_of_ids) == 0:
return JSONResponse(
content=({"success": False, "message": "Not a valid list of ids"}),
status_code=404,
)
reviews = ( reviews = (
ReviewSegment.select( ReviewSegment.select(
ReviewSegment.camera, ReviewSegment.camera,
@ -424,7 +366,9 @@ def delete_reviews(body: dict = None):
) )
@router.get("/review/activity/motion") @router.get(
"/review/activity/motion", response_model=list[ReviewActivityMotionResponse]
)
def motion_activity(params: ReviewActivityMotionQueryParams = Depends()): def motion_activity(params: ReviewActivityMotionQueryParams = Depends()):
"""Get motion and audio activity.""" """Get motion and audio activity."""
cameras = params.cameras cameras = params.cameras
@ -498,98 +442,44 @@ def motion_activity(params: ReviewActivityMotionQueryParams = Depends()):
return JSONResponse(content=normalized) return JSONResponse(content=normalized)
@router.get("/review/activity/audio") @router.get("/review/event/{event_id}", response_model=ReviewSegmentResponse)
def audio_activity(params: ReviewActivityMotionQueryParams = Depends()):
"""Get motion and audio activity."""
cameras = params.cameras
before = params.before or datetime.datetime.now().timestamp()
after = (
params.after
or (datetime.datetime.now() - datetime.timedelta(hours=1)).timestamp()
)
# get scale in seconds
scale = params.scale
clauses = [(Recordings.start_time > after) & (Recordings.end_time < before)]
if cameras != "all":
camera_list = cameras.split(",")
clauses.append((Recordings.camera << camera_list))
all_recordings: list[Recordings] = (
Recordings.select(
Recordings.start_time,
Recordings.duration,
Recordings.objects,
Recordings.dBFS,
)
.where(reduce(operator.and_, clauses))
.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 audio to be scaled down
data: list[dict[str, float]] = []
for rec in all_recordings:
data.append(
{
"start_time": rec.start_time,
"audio": rec.dBFS if rec.objects == 0 else 0,
}
)
# resample data using pandas to get activity on scaled basis
df = pd.DataFrame(data, columns=["start_time", "audio"])
df = df.astype(dtype={"audio": "float16"})
# set date as datetime index
df["start_time"] = pd.to_datetime(df["start_time"], unit="s")
df.set_index(["start_time"], inplace=True)
# normalize data
df = df.resample(f"{scale}S").mean().fillna(0.0)
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 JSONResponse(content=normalized)
@router.get("/review/event/{event_id}")
def get_review_from_event(event_id: str): def get_review_from_event(event_id: str):
try: try:
return model_to_dict( return JSONResponse(
model_to_dict(
ReviewSegment.get( ReviewSegment.get(
ReviewSegment.data["detections"].cast("text") % f'*"{event_id}"*' ReviewSegment.data["detections"].cast("text") % f'*"{event_id}"*'
) )
) )
)
except DoesNotExist: except DoesNotExist:
return "Review item not found", 404 return JSONResponse(
content={"success": False, "message": "Review item not found"},
status_code=404,
)
@router.get("/review/{event_id}") @router.get("/review/{review_id}", response_model=ReviewSegmentResponse)
def get_review(event_id: str): def get_review(review_id: str):
try: try:
return model_to_dict(ReviewSegment.get(ReviewSegment.id == event_id)) return JSONResponse(
content=model_to_dict(ReviewSegment.get(ReviewSegment.id == review_id))
)
except DoesNotExist: except DoesNotExist:
return "Review item not found", 404 return JSONResponse(
content={"success": False, "message": "Review item not found"},
status_code=404,
)
@router.delete("/review/{event_id}/viewed") @router.delete("/review/{review_id}/viewed", response_model=GenericResponse)
def set_not_reviewed(event_id: str): def set_not_reviewed(review_id: str):
try: try:
review: ReviewSegment = ReviewSegment.get(ReviewSegment.id == event_id) review: ReviewSegment = ReviewSegment.get(ReviewSegment.id == review_id)
except DoesNotExist: except DoesNotExist:
return JSONResponse( return JSONResponse(
content=( content=(
{"success": False, "message": "Review " + event_id + " not found"} {"success": False, "message": "Review " + review_id + " not found"}
), ),
status_code=404, status_code=404,
) )
@ -598,6 +488,8 @@ def set_not_reviewed(event_id: str):
review.save() review.save()
return JSONResponse( return JSONResponse(
content=({"success": True, "message": "Reviewed " + event_id + " not viewed"}), content=(
{"success": True, "message": "Set Review " + review_id + " as not viewed"}
),
status_code=200, status_code=200,
) )

View File

@ -93,7 +93,7 @@ class ReviewSegment(Model): # type: ignore[misc]
start_time = DateTimeField() start_time = DateTimeField()
end_time = DateTimeField() end_time = DateTimeField()
has_been_reviewed = BooleanField(default=False) has_been_reviewed = BooleanField(default=False)
severity = CharField(max_length=30) # alert, detection, significant_motion severity = CharField(max_length=30) # alert, detection
thumb_path = CharField(unique=True) thumb_path = CharField(unique=True)
data = JSONField() # additional data about detection like list of labels, zone, areas of significant motion data = JSONField() # additional data about detection like list of labels, zone, areas of significant motion

View File

@ -407,10 +407,6 @@ export default function Events() {
review.severity == "detection" review.severity == "detection"
? item.reviewed_detection + 1 ? item.reviewed_detection + 1
: item.reviewed_detection, : item.reviewed_detection,
reviewed_motion:
review.severity == "significant_motion"
? item.reviewed_motion + 1
: item.reviewed_motion,
}, },
}; };
}, },

View File

@ -42,10 +42,8 @@ type ReviewSummaryDay = {
day: string; day: string;
reviewed_alert: number; reviewed_alert: number;
reviewed_detection: number; reviewed_detection: number;
reviewed_motion: number;
total_alert: number; total_alert: number;
total_detection: number; total_detection: number;
total_motion: number;
}; };
export type ReviewSummary = { export type ReviewSummary = {

View File

@ -117,13 +117,11 @@ export default function EventView({
return { return {
alert: summary.total_alert ?? 0, alert: summary.total_alert ?? 0,
detection: summary.total_detection ?? 0, detection: summary.total_detection ?? 0,
significant_motion: summary.total_motion ?? 0,
}; };
} else { } else {
return { return {
alert: summary.total_alert - summary.reviewed_alert, alert: summary.total_alert - summary.reviewed_alert,
detection: summary.total_detection - summary.reviewed_detection, detection: summary.total_detection - summary.reviewed_detection,
significant_motion: summary.total_motion - summary.reviewed_motion,
}; };
} }
}, [filter, showReviewed, reviewSummary]); }, [filter, showReviewed, reviewSummary]);