mirror of
https://github.com/blakeblackshear/frigate.git
synced 2025-07-30 13:48:07 +02:00
Merge remote-tracking branch 'upstream/dev' into dev
This commit is contained in:
commit
126e3bdcc9
1199
docs/static/frigate-api.yaml
vendored
1199
docs/static/frigate-api.yaml
vendored
File diff suppressed because it is too large
Load Diff
@ -17,8 +17,8 @@ from fastapi.responses import JSONResponse, PlainTextResponse
|
|||||||
from markupsafe import escape
|
from markupsafe import escape
|
||||||
from peewee import operator
|
from peewee import operator
|
||||||
|
|
||||||
from frigate.api.defs.app_body import AppConfigSetBody
|
from frigate.api.defs.query.app_query_parameters import AppTimelineHourlyQueryParameters
|
||||||
from frigate.api.defs.app_query_parameters import AppTimelineHourlyQueryParameters
|
from frigate.api.defs.request.app_body import AppConfigSetBody
|
||||||
from frigate.api.defs.tags import Tags
|
from frigate.api.defs.tags import Tags
|
||||||
from frigate.config import FrigateConfig
|
from frigate.config import FrigateConfig
|
||||||
from frigate.const import CONFIG_DIR
|
from frigate.const import CONFIG_DIR
|
||||||
|
@ -18,7 +18,7 @@ from joserfc import jwt
|
|||||||
from peewee import DoesNotExist
|
from peewee import DoesNotExist
|
||||||
from slowapi import Limiter
|
from slowapi import Limiter
|
||||||
|
|
||||||
from frigate.api.defs.app_body import (
|
from frigate.api.defs.request.app_body import (
|
||||||
AppPostLoginBody,
|
AppPostLoginBody,
|
||||||
AppPostUsersBody,
|
AppPostUsersBody,
|
||||||
AppPutPasswordBody,
|
AppPutPasswordBody,
|
||||||
@ -85,7 +85,12 @@ def get_remote_addr(request: Request):
|
|||||||
return str(ip)
|
return str(ip)
|
||||||
|
|
||||||
# if there wasn't anything in the route, just return the default
|
# if there wasn't anything in the route, just return the default
|
||||||
return request.remote_addr or "127.0.0.1"
|
remote_addr = None
|
||||||
|
|
||||||
|
if hasattr(request, "remote_addr"):
|
||||||
|
remote_addr = request.remote_addr
|
||||||
|
|
||||||
|
return remote_addr or "127.0.0.1"
|
||||||
|
|
||||||
|
|
||||||
def get_jwt_secret() -> str:
|
def get_jwt_secret() -> str:
|
||||||
|
42
frigate/api/defs/response/event_response.py
Normal file
42
frigate/api/defs/response/event_response.py
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
from typing import Any, Optional
|
||||||
|
|
||||||
|
from pydantic import BaseModel, ConfigDict
|
||||||
|
|
||||||
|
|
||||||
|
class EventResponse(BaseModel):
|
||||||
|
id: str
|
||||||
|
label: str
|
||||||
|
sub_label: Optional[str]
|
||||||
|
camera: str
|
||||||
|
start_time: float
|
||||||
|
end_time: Optional[float]
|
||||||
|
false_positive: Optional[bool]
|
||||||
|
zones: list[str]
|
||||||
|
thumbnail: str
|
||||||
|
has_clip: bool
|
||||||
|
has_snapshot: bool
|
||||||
|
retain_indefinitely: bool
|
||||||
|
plus_id: Optional[str]
|
||||||
|
model_hash: Optional[str]
|
||||||
|
detector_type: Optional[str]
|
||||||
|
model_type: Optional[str]
|
||||||
|
data: dict[str, Any]
|
||||||
|
|
||||||
|
model_config = ConfigDict(protected_namespaces=())
|
||||||
|
|
||||||
|
|
||||||
|
class EventCreateResponse(BaseModel):
|
||||||
|
success: bool
|
||||||
|
message: str
|
||||||
|
event_id: str
|
||||||
|
|
||||||
|
|
||||||
|
class EventMultiDeleteResponse(BaseModel):
|
||||||
|
success: bool
|
||||||
|
deleted_events: list[str]
|
||||||
|
not_found_events: list[str]
|
||||||
|
|
||||||
|
|
||||||
|
class EventUploadPlusResponse(BaseModel):
|
||||||
|
success: bool
|
||||||
|
plus_id: str
|
@ -14,7 +14,16 @@ from fastapi.responses import JSONResponse
|
|||||||
from peewee import JOIN, DoesNotExist, fn, operator
|
from peewee import JOIN, DoesNotExist, fn, operator
|
||||||
from playhouse.shortcuts import model_to_dict
|
from playhouse.shortcuts import model_to_dict
|
||||||
|
|
||||||
from frigate.api.defs.events_body import (
|
from frigate.api.defs.query.events_query_parameters import (
|
||||||
|
DEFAULT_TIME_RANGE,
|
||||||
|
EventsQueryParams,
|
||||||
|
EventsSearchQueryParams,
|
||||||
|
EventsSummaryQueryParams,
|
||||||
|
)
|
||||||
|
from frigate.api.defs.query.regenerate_query_parameters import (
|
||||||
|
RegenerateQueryParameters,
|
||||||
|
)
|
||||||
|
from frigate.api.defs.request.events_body import (
|
||||||
EventsCreateBody,
|
EventsCreateBody,
|
||||||
EventsDeleteBody,
|
EventsDeleteBody,
|
||||||
EventsDescriptionBody,
|
EventsDescriptionBody,
|
||||||
@ -22,19 +31,15 @@ from frigate.api.defs.events_body import (
|
|||||||
EventsSubLabelBody,
|
EventsSubLabelBody,
|
||||||
SubmitPlusBody,
|
SubmitPlusBody,
|
||||||
)
|
)
|
||||||
from frigate.api.defs.events_query_parameters import (
|
from frigate.api.defs.response.event_response import (
|
||||||
DEFAULT_TIME_RANGE,
|
EventCreateResponse,
|
||||||
EventsQueryParams,
|
EventMultiDeleteResponse,
|
||||||
EventsSearchQueryParams,
|
EventResponse,
|
||||||
EventsSummaryQueryParams,
|
EventUploadPlusResponse,
|
||||||
)
|
|
||||||
from frigate.api.defs.regenerate_query_parameters import (
|
|
||||||
RegenerateQueryParameters,
|
|
||||||
)
|
)
|
||||||
|
from frigate.api.defs.response.generic_response import GenericResponse
|
||||||
from frigate.api.defs.tags import Tags
|
from frigate.api.defs.tags import Tags
|
||||||
from frigate.const import (
|
from frigate.const import CLIPS_DIR
|
||||||
CLIPS_DIR,
|
|
||||||
)
|
|
||||||
from frigate.embeddings import EmbeddingsContext
|
from frigate.embeddings import EmbeddingsContext
|
||||||
from frigate.events.external import ExternalEventProcessor
|
from frigate.events.external import ExternalEventProcessor
|
||||||
from frigate.models import Event, ReviewSegment, Timeline
|
from frigate.models import Event, ReviewSegment, Timeline
|
||||||
@ -46,7 +51,7 @@ logger = logging.getLogger(__name__)
|
|||||||
router = APIRouter(tags=[Tags.events])
|
router = APIRouter(tags=[Tags.events])
|
||||||
|
|
||||||
|
|
||||||
@router.get("/events")
|
@router.get("/events", response_model=list[EventResponse])
|
||||||
def events(params: EventsQueryParams = Depends()):
|
def events(params: EventsQueryParams = Depends()):
|
||||||
camera = params.camera
|
camera = params.camera
|
||||||
cameras = params.cameras
|
cameras = params.cameras
|
||||||
@ -265,7 +270,7 @@ def events(params: EventsQueryParams = Depends()):
|
|||||||
return JSONResponse(content=list(events))
|
return JSONResponse(content=list(events))
|
||||||
|
|
||||||
|
|
||||||
@router.get("/events/explore")
|
@router.get("/events/explore", response_model=list[EventResponse])
|
||||||
def events_explore(limit: int = 10):
|
def events_explore(limit: int = 10):
|
||||||
# get distinct labels for all events
|
# get distinct labels for all events
|
||||||
distinct_labels = Event.select(Event.label).distinct().order_by(Event.label)
|
distinct_labels = Event.select(Event.label).distinct().order_by(Event.label)
|
||||||
@ -310,7 +315,8 @@ def events_explore(limit: int = 10):
|
|||||||
"data": {
|
"data": {
|
||||||
k: v
|
k: v
|
||||||
for k, v in event.data.items()
|
for k, v in event.data.items()
|
||||||
if k in ["type", "score", "top_score", "description"]
|
if k
|
||||||
|
in ["type", "score", "top_score", "description", "sub_label_score"]
|
||||||
},
|
},
|
||||||
"event_count": label_counts[event.label],
|
"event_count": label_counts[event.label],
|
||||||
}
|
}
|
||||||
@ -326,7 +332,7 @@ def events_explore(limit: int = 10):
|
|||||||
return JSONResponse(content=processed_events)
|
return JSONResponse(content=processed_events)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/event_ids")
|
@router.get("/event_ids", response_model=list[EventResponse])
|
||||||
def event_ids(ids: str):
|
def event_ids(ids: str):
|
||||||
ids = ids.split(",")
|
ids = ids.split(",")
|
||||||
|
|
||||||
@ -647,7 +653,7 @@ def events_summary(params: EventsSummaryQueryParams = Depends()):
|
|||||||
return JSONResponse(content=[e for e in groups.dicts()])
|
return JSONResponse(content=[e for e in groups.dicts()])
|
||||||
|
|
||||||
|
|
||||||
@router.get("/events/{event_id}")
|
@router.get("/events/{event_id}", response_model=EventResponse)
|
||||||
def event(event_id: str):
|
def event(event_id: str):
|
||||||
try:
|
try:
|
||||||
return model_to_dict(Event.get(Event.id == event_id))
|
return model_to_dict(Event.get(Event.id == event_id))
|
||||||
@ -655,7 +661,7 @@ def event(event_id: str):
|
|||||||
return JSONResponse(content="Event not found", status_code=404)
|
return JSONResponse(content="Event not found", status_code=404)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/events/{event_id}/retain")
|
@router.post("/events/{event_id}/retain", response_model=GenericResponse)
|
||||||
def set_retain(event_id: str):
|
def set_retain(event_id: str):
|
||||||
try:
|
try:
|
||||||
event = Event.get(Event.id == event_id)
|
event = Event.get(Event.id == event_id)
|
||||||
@ -674,7 +680,7 @@ def set_retain(event_id: str):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/events/{event_id}/plus")
|
@router.post("/events/{event_id}/plus", response_model=EventUploadPlusResponse)
|
||||||
def send_to_plus(request: Request, event_id: str, body: SubmitPlusBody = None):
|
def send_to_plus(request: Request, event_id: str, body: SubmitPlusBody = None):
|
||||||
if not request.app.frigate_config.plus_api.is_active():
|
if not request.app.frigate_config.plus_api.is_active():
|
||||||
message = "PLUS_API_KEY environment variable is not set"
|
message = "PLUS_API_KEY environment variable is not set"
|
||||||
@ -786,7 +792,7 @@ def send_to_plus(request: Request, event_id: str, body: SubmitPlusBody = None):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.put("/events/{event_id}/false_positive")
|
@router.put("/events/{event_id}/false_positive", response_model=EventUploadPlusResponse)
|
||||||
def false_positive(request: Request, event_id: str):
|
def false_positive(request: Request, event_id: str):
|
||||||
if not request.app.frigate_config.plus_api.is_active():
|
if not request.app.frigate_config.plus_api.is_active():
|
||||||
message = "PLUS_API_KEY environment variable is not set"
|
message = "PLUS_API_KEY environment variable is not set"
|
||||||
@ -875,7 +881,7 @@ def false_positive(request: Request, event_id: str):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/events/{event_id}/retain")
|
@router.delete("/events/{event_id}/retain", response_model=GenericResponse)
|
||||||
def delete_retain(event_id: str):
|
def delete_retain(event_id: str):
|
||||||
try:
|
try:
|
||||||
event = Event.get(Event.id == event_id)
|
event = Event.get(Event.id == event_id)
|
||||||
@ -894,7 +900,7 @@ def delete_retain(event_id: str):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/events/{event_id}/sub_label")
|
@router.post("/events/{event_id}/sub_label", response_model=GenericResponse)
|
||||||
def set_sub_label(
|
def set_sub_label(
|
||||||
request: Request,
|
request: Request,
|
||||||
event_id: str,
|
event_id: str,
|
||||||
@ -946,7 +952,7 @@ def set_sub_label(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/events/{event_id}/description")
|
@router.post("/events/{event_id}/description", response_model=GenericResponse)
|
||||||
def set_description(
|
def set_description(
|
||||||
request: Request,
|
request: Request,
|
||||||
event_id: str,
|
event_id: str,
|
||||||
@ -993,7 +999,7 @@ def set_description(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.put("/events/{event_id}/description/regenerate")
|
@router.put("/events/{event_id}/description/regenerate", response_model=GenericResponse)
|
||||||
def regenerate_description(
|
def regenerate_description(
|
||||||
request: Request, event_id: str, params: RegenerateQueryParameters = Depends()
|
request: Request, event_id: str, params: RegenerateQueryParameters = Depends()
|
||||||
):
|
):
|
||||||
@ -1064,14 +1070,14 @@ def delete_single_event(event_id: str, request: Request) -> dict:
|
|||||||
return {"success": True, "message": f"Event {event_id} deleted"}
|
return {"success": True, "message": f"Event {event_id} deleted"}
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/events/{event_id}")
|
@router.delete("/events/{event_id}", response_model=GenericResponse)
|
||||||
def delete_event(request: Request, event_id: str):
|
def delete_event(request: Request, event_id: str):
|
||||||
result = delete_single_event(event_id, request)
|
result = delete_single_event(event_id, request)
|
||||||
status_code = 200 if result["success"] else 404
|
status_code = 200 if result["success"] else 404
|
||||||
return JSONResponse(content=result, status_code=status_code)
|
return JSONResponse(content=result, status_code=status_code)
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/events/")
|
@router.delete("/events/", response_model=EventMultiDeleteResponse)
|
||||||
def delete_events(request: Request, body: EventsDeleteBody):
|
def delete_events(request: Request, body: EventsDeleteBody):
|
||||||
if not body.event_ids:
|
if not body.event_ids:
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
@ -1097,7 +1103,7 @@ def delete_events(request: Request, body: EventsDeleteBody):
|
|||||||
return JSONResponse(content=response, status_code=200)
|
return JSONResponse(content=response, status_code=200)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/events/{camera_name}/{label}/create")
|
@router.post("/events/{camera_name}/{label}/create", response_model=EventCreateResponse)
|
||||||
def create_event(
|
def create_event(
|
||||||
request: Request,
|
request: Request,
|
||||||
camera_name: str,
|
camera_name: str,
|
||||||
@ -1153,7 +1159,7 @@ def create_event(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.put("/events/{event_id}/end")
|
@router.put("/events/{event_id}/end", response_model=GenericResponse)
|
||||||
def end_event(request: Request, event_id: str, body: EventsEndBody):
|
def end_event(request: Request, event_id: str, body: EventsEndBody):
|
||||||
try:
|
try:
|
||||||
end_time = body.end_time or datetime.datetime.now().timestamp()
|
end_time = body.end_time or datetime.datetime.now().timestamp()
|
||||||
|
@ -9,6 +9,7 @@ import psutil
|
|||||||
from fastapi import APIRouter, Request
|
from fastapi import APIRouter, Request
|
||||||
from fastapi.responses import JSONResponse
|
from fastapi.responses import JSONResponse
|
||||||
from peewee import DoesNotExist
|
from peewee import DoesNotExist
|
||||||
|
from playhouse.shortcuts import model_to_dict
|
||||||
|
|
||||||
from frigate.api.defs.request.export_recordings_body import ExportRecordingsBody
|
from frigate.api.defs.request.export_recordings_body import ExportRecordingsBody
|
||||||
from frigate.api.defs.tags import Tags
|
from frigate.api.defs.tags import Tags
|
||||||
@ -207,3 +208,14 @@ def export_delete(event_id: str):
|
|||||||
),
|
),
|
||||||
status_code=200,
|
status_code=200,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/exports/{export_id}")
|
||||||
|
def get_export(export_id: str):
|
||||||
|
try:
|
||||||
|
return JSONResponse(content=model_to_dict(Export.get(Export.id == export_id)))
|
||||||
|
except DoesNotExist:
|
||||||
|
return JSONResponse(
|
||||||
|
content={"success": False, "message": "Export not found"},
|
||||||
|
status_code=404,
|
||||||
|
)
|
||||||
|
@ -20,7 +20,7 @@ from pathvalidate import sanitize_filename
|
|||||||
from peewee import DoesNotExist, fn
|
from peewee import DoesNotExist, fn
|
||||||
from tzlocal import get_localzone_name
|
from tzlocal import get_localzone_name
|
||||||
|
|
||||||
from frigate.api.defs.media_query_parameters import (
|
from frigate.api.defs.query.media_query_parameters import (
|
||||||
Extension,
|
Extension,
|
||||||
MediaEventsSnapshotQueryParams,
|
MediaEventsSnapshotQueryParams,
|
||||||
MediaLatestFrameQueryParams,
|
MediaLatestFrameQueryParams,
|
||||||
|
@ -12,14 +12,14 @@ 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.query.review_query_parameters import (
|
||||||
from frigate.api.defs.review_body import ReviewModifyMultipleBody
|
|
||||||
from frigate.api.defs.review_query_parameters import (
|
|
||||||
ReviewActivityMotionQueryParams,
|
ReviewActivityMotionQueryParams,
|
||||||
ReviewQueryParams,
|
ReviewQueryParams,
|
||||||
ReviewSummaryQueryParams,
|
ReviewSummaryQueryParams,
|
||||||
)
|
)
|
||||||
from frigate.api.defs.review_responses import (
|
from frigate.api.defs.request.review_body import ReviewModifyMultipleBody
|
||||||
|
from frigate.api.defs.response.generic_response import GenericResponse
|
||||||
|
from frigate.api.defs.response.review_response import (
|
||||||
ReviewActivityMotionResponse,
|
ReviewActivityMotionResponse,
|
||||||
ReviewSegmentResponse,
|
ReviewSegmentResponse,
|
||||||
ReviewSummaryResponse,
|
ReviewSummaryResponse,
|
||||||
@ -364,7 +364,7 @@ def delete_reviews(body: ReviewModifyMultipleBody):
|
|||||||
ReviewSegment.delete().where(ReviewSegment.id << list_of_ids).execute()
|
ReviewSegment.delete().where(ReviewSegment.id << list_of_ids).execute()
|
||||||
|
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
content=({"success": True, "message": "Delete reviews"}), status_code=200
|
content=({"success": True, "message": "Deleted review items."}), status_code=200
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -525,7 +525,7 @@ class TestHttpReview(BaseTestHttp):
|
|||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
response_json = response.json()
|
response_json = response.json()
|
||||||
assert response_json["success"] == True
|
assert response_json["success"] == True
|
||||||
assert response_json["message"] == "Delete reviews"
|
assert response_json["message"] == "Deleted review items."
|
||||||
# Verify that in DB the review segment was not deleted
|
# Verify that in DB the review segment was not deleted
|
||||||
review_ids_in_db_after = self._get_reviews([id])
|
review_ids_in_db_after = self._get_reviews([id])
|
||||||
assert len(review_ids_in_db_after) == 1
|
assert len(review_ids_in_db_after) == 1
|
||||||
@ -540,7 +540,7 @@ class TestHttpReview(BaseTestHttp):
|
|||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
response_json = response.json()
|
response_json = response.json()
|
||||||
assert response_json["success"] == True
|
assert response_json["success"] == True
|
||||||
assert response_json["message"] == "Delete reviews"
|
assert response_json["message"] == "Deleted review items."
|
||||||
# Verify that in DB the review segment was deleted
|
# Verify that in DB the review segment was deleted
|
||||||
review_ids_in_db_after = self._get_reviews([id])
|
review_ids_in_db_after = self._get_reviews([id])
|
||||||
assert len(review_ids_in_db_after) == 0
|
assert len(review_ids_in_db_after) == 0
|
||||||
@ -562,7 +562,7 @@ class TestHttpReview(BaseTestHttp):
|
|||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
response_json = response.json()
|
response_json = response.json()
|
||||||
assert response_json["success"] == True
|
assert response_json["success"] == True
|
||||||
assert response_json["message"] == "Delete reviews"
|
assert response_json["message"] == "Deleted review items."
|
||||||
|
|
||||||
# Verify that in DB all review segments and recordings that were passed were deleted
|
# Verify that in DB all review segments and recordings that were passed were deleted
|
||||||
review_ids_in_db_after = self._get_reviews(ids)
|
review_ids_in_db_after = self._get_reviews(ids)
|
||||||
|
@ -168,7 +168,7 @@ class TestHttp(unittest.TestCase):
|
|||||||
|
|
||||||
assert event
|
assert event
|
||||||
assert event["id"] == id
|
assert event["id"] == id
|
||||||
assert event == model_to_dict(Event.get(Event.id == id))
|
assert event["id"] == model_to_dict(Event.get(Event.id == id))["id"]
|
||||||
|
|
||||||
def test_get_bad_event(self):
|
def test_get_bad_event(self):
|
||||||
app = create_fastapi_app(
|
app = create_fastapi_app(
|
||||||
|
@ -219,6 +219,8 @@ def draw_box_with_label(
|
|||||||
text_width = size[0][0]
|
text_width = size[0][0]
|
||||||
text_height = size[0][1]
|
text_height = size[0][1]
|
||||||
line_height = text_height + size[1]
|
line_height = text_height + size[1]
|
||||||
|
# get frame height
|
||||||
|
frame_height = frame.shape[0]
|
||||||
# set the text start position
|
# set the text start position
|
||||||
if position == "ul":
|
if position == "ul":
|
||||||
text_offset_x = x_min
|
text_offset_x = x_min
|
||||||
@ -228,18 +230,23 @@ def draw_box_with_label(
|
|||||||
text_offset_y = max(0, y_min - (line_height + 8))
|
text_offset_y = max(0, y_min - (line_height + 8))
|
||||||
elif position == "bl":
|
elif position == "bl":
|
||||||
text_offset_x = x_min
|
text_offset_x = x_min
|
||||||
text_offset_y = y_max
|
text_offset_y = min(frame_height - line_height, y_max)
|
||||||
elif position == "br":
|
elif position == "br":
|
||||||
text_offset_x = max(0, x_max - (text_width + 8))
|
text_offset_x = max(0, x_max - (text_width + 8))
|
||||||
text_offset_y = y_max
|
text_offset_y = min(frame_height - line_height, y_max)
|
||||||
|
# Adjust position if it overlaps with the box or goes out of frame
|
||||||
# Adjust position if it overlaps with the box
|
if position in {"ul", "ur"}:
|
||||||
if position in {"ul", "ur"} and text_offset_y < y_min + thickness:
|
if text_offset_y < y_min + thickness: # Label overlaps with the box
|
||||||
# Move the text below the box
|
if y_min - (line_height + 8) < 0 and y_max + line_height <= frame_height:
|
||||||
text_offset_y = y_max
|
# Not enough space above, and there is space below
|
||||||
elif position in {"bl", "br"} and text_offset_y + line_height > y_max:
|
text_offset_y = y_max
|
||||||
# Move the text above the box
|
elif y_min - (line_height + 8) >= 0:
|
||||||
text_offset_y = max(0, y_min - (line_height + 8))
|
# Enough space above, keep the label at the top
|
||||||
|
text_offset_y = max(0, y_min - (line_height + 8))
|
||||||
|
elif position in {"bl", "br"}:
|
||||||
|
if text_offset_y + line_height > frame_height:
|
||||||
|
# If there's not enough space below, try above the box
|
||||||
|
text_offset_y = max(0, y_min - (line_height + 8))
|
||||||
|
|
||||||
# make the coords of the box with a small padding of two pixels
|
# make the coords of the box with a small padding of two pixels
|
||||||
textbox_coords = (
|
textbox_coords = (
|
||||||
|
Loading…
Reference in New Issue
Block a user