Skip to content

Commit dcbbf56

Browse files
authored
Merge pull request #33 from hawkeye217/multi-stream
Multi stream
2 parents a2659f2 + 0bb02b0 commit dcbbf56

File tree

30 files changed

+900
-100
lines changed

30 files changed

+900
-100
lines changed

docker/main/install_deps.sh

+2-2
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,8 @@ if [[ "${TARGETARCH}" == "amd64" ]]; then
8787
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/intel-graphics.gpg] https://repositories.intel.com/gpu/ubuntu jammy client" | tee /etc/apt/sources.list.d/intel-gpu-jammy.list
8888
apt-get -qq update
8989
apt-get -qq install --no-install-recommends --no-install-suggests -y \
90-
intel-opencl-icd intel-level-zero-gpu intel-media-va-driver-non-free \
91-
libmfx1 libmfxgen1 libvpl2
90+
intel-opencl-icd=24.35.30872.31-996~22.04 intel-level-zero-gpu=1.3.29735.27-914~22.04 intel-media-va-driver-non-free=24.3.3-996~22.04 \
91+
libmfx1=23.2.2-880~22.04 libmfxgen1=24.2.4-914~22.04 libvpl2=1:2.13.0.0-996~22.04
9292

9393
rm -f /usr/share/keyrings/intel-graphics.gpg
9494
rm -f /etc/apt/sources.list.d/intel-gpu-jammy.list

docker/tensorrt/detector/rootfs/etc/s6-overlay/s6-rc.d/trt-model-prepare/run

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ set -o errexit -o nounset -o pipefail
1111
MODEL_CACHE_DIR=${MODEL_CACHE_DIR:-"/config/model_cache/tensorrt"}
1212
TRT_VER=${TRT_VER:-$(cat /etc/TENSORRT_VER)}
1313
OUTPUT_FOLDER="${MODEL_CACHE_DIR}/${TRT_VER}"
14+
YOLO_MODELS=${YOLO_MODELS:-""}
1415

1516
# Create output folder
1617
mkdir -p ${OUTPUT_FOLDER}

docs/docs/configuration/live.md

+11-3
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,15 @@ go2rtc:
5151
- ffmpeg:rtsp://192.168.1.5:554/live0#video=copy
5252
```
5353
54-
### Setting Stream For Live UI
54+
### Setting Streams For Live UI
5555
56-
There may be some cameras that you would prefer to use the sub stream for live view, but the main stream for recording. This can be done via `live -> stream_name`.
56+
In Frigate 0.16 and later, you can configure Live view to allow manual selection of the stream you want to view in the Live UI. For example, you may want to view your camera's substream on mobile devices, but your full resolution stream on desktop devices. Setting the `live -> streams` list will populate a dropdown in the UI's Live view that allows you to select a stream for live viewing.
57+
58+
Additionally, when creating and editing camera groups in the UI, you can choose the stream you want to use for your camera group's Live dashboard.
59+
60+
Configure the `streams` option with a "friendly name" for your stream followed by the go2rtc stream name.
61+
62+
Go2rtc is required to use this feature. You cannot specify paths in the `streams` list, only go2rtc stream names.
5763

5864
```yaml
5965
go2rtc:
@@ -80,7 +86,9 @@ cameras:
8086
roles:
8187
- detect
8288
live:
83-
stream_name: test_cam_sub
89+
streams:
90+
- Main Stream: test_cam
91+
- Sub Stream: test_cam_sub
8492
```
8593

8694
### WebRTC extra configuration:

docs/docs/configuration/reference.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -559,10 +559,11 @@ go2rtc:
559559
# Optional: Live stream configuration for WebUI.
560560
# NOTE: Can be overridden at the camera level
561561
live:
562-
# Optional: Set the name of the stream configured in go2rtc
562+
# Optional: Set the streams configured in go2rtc
563563
# that should be used for live view in frigate WebUI. (default: name of camera)
564564
# NOTE: In most cases this should be set at the camera level only.
565-
stream_name: camera_name
565+
streams:
566+
- friendly_name: stream_name
566567
# Optional: Set the height of the jsmpeg stream. (default: 720)
567568
# This must be less than or equal to the height of the detect stream. Lower resolutions
568569
# reduce bandwidth required for viewing the jsmpeg stream. Width is computed to match known aspect ratio.

frigate/api/defs/events_query_parameters.py

+1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ class EventsSearchQueryParams(BaseModel):
4747
time_range: Optional[str] = DEFAULT_TIME_RANGE
4848
has_clip: Optional[bool] = None
4949
has_snapshot: Optional[bool] = None
50+
is_submitted: Optional[bool] = None
5051
timezone: Optional[str] = "utc"
5152
min_score: Optional[float] = None
5253
max_score: Optional[float] = None

frigate/api/event.py

+7
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,7 @@ def events_search(request: Request, params: EventsSearchQueryParams = Depends())
360360
time_range = params.time_range
361361
has_clip = params.has_clip
362362
has_snapshot = params.has_snapshot
363+
is_submitted = params.is_submitted
363364

364365
# for similarity search
365366
event_id = params.event_id
@@ -441,6 +442,12 @@ def events_search(request: Request, params: EventsSearchQueryParams = Depends())
441442
if has_snapshot is not None:
442443
event_filters.append((Event.has_snapshot == has_snapshot))
443444

445+
if is_submitted is not None:
446+
if is_submitted == 0:
447+
event_filters.append((Event.plus_id.is_null()))
448+
elif is_submitted > 0:
449+
event_filters.append((Event.plus_id != ""))
450+
444451
if min_score is not None and max_score is not None:
445452
event_filters.append((Event.data["score"].between(min_score, max_score)))
446453
else:

frigate/app.py

+9-5
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@
6363
from frigate.record.export import migrate_exports
6464
from frigate.record.record import manage_recordings
6565
from frigate.review.review import manage_review_segments
66-
from frigate.service_manager import ServiceManager
6766
from frigate.stats.emitter import StatsEmitter
6867
from frigate.stats.util import stats_init
6968
from frigate.storage import StorageMaintainer
@@ -79,6 +78,7 @@
7978

8079
class FrigateApp:
8180
def __init__(self, config: FrigateConfig) -> None:
81+
self.audio_process: Optional[mp.Process] = None
8282
self.stop_event: MpEvent = mp.Event()
8383
self.detection_queue: Queue = mp.Queue()
8484
self.detectors: dict[str, ObjectDetectProcess] = {}
@@ -449,8 +449,9 @@ def start_audio_processor(self) -> None:
449449
]
450450

451451
if audio_cameras:
452-
proc = AudioProcessor(audio_cameras, self.camera_metrics).start(wait=True)
453-
self.processes["audio_detector"] = proc.pid or 0
452+
self.audio_process = AudioProcessor(audio_cameras, self.camera_metrics)
453+
self.audio_process.start()
454+
self.processes["audio_detector"] = self.audio_process.pid or 0
454455

455456
def start_timeline_processor(self) -> None:
456457
self.timeline_processor = TimelineProcessor(
@@ -641,6 +642,11 @@ def stop(self) -> None:
641642
ReviewSegment.end_time == None
642643
).execute()
643644

645+
# stop the audio process
646+
if self.audio_process:
647+
self.audio_process.terminate()
648+
self.audio_process.join()
649+
644650
# ensure the capture processes are done
645651
for camera, metrics in self.camera_metrics.items():
646652
capture_process = metrics.capture_process
@@ -709,6 +715,4 @@ def stop(self) -> None:
709715
shm.close()
710716
shm.unlink()
711717

712-
ServiceManager.current().shutdown(wait=True)
713-
714718
os._exit(os.EX_OK)

frigate/config/camera/live.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from typing import Dict
2+
13
from pydantic import Field
24

35
from ..base import FrigateBaseModel
@@ -6,6 +8,9 @@
68

79

810
class CameraLiveConfig(FrigateBaseModel):
9-
stream_name: str = Field(default="", title="Name of restream to use as live view.")
11+
streams: Dict[str, str] = Field(
12+
default_factory=list,
13+
title="Friendly names and restream names to use for live view.",
14+
)
1015
height: int = Field(default=720, title="Live camera view height")
1116
quality: int = Field(default=8, ge=1, le=31, title="Live camera view quality")

frigate/config/config.py

+12-11
Original file line numberDiff line numberDiff line change
@@ -190,17 +190,18 @@ def verify_config_roles(camera_config: CameraConfig) -> None:
190190
)
191191

192192

193-
def verify_valid_live_stream_name(
193+
def verify_valid_live_stream_names(
194194
frigate_config: FrigateConfig, camera_config: CameraConfig
195195
) -> ValueError | None:
196196
"""Verify that a restream exists to use for live view."""
197-
if (
198-
camera_config.live.stream_name
199-
not in frigate_config.go2rtc.model_dump().get("streams", {}).keys()
200-
):
201-
return ValueError(
202-
f"No restream with name {camera_config.live.stream_name} exists for camera {camera_config.name}."
203-
)
197+
for _, stream_name in camera_config.live.streams.items():
198+
if (
199+
stream_name
200+
not in frigate_config.go2rtc.model_dump().get("streams", {}).keys()
201+
):
202+
return ValueError(
203+
f"No restream with name {stream_name} exists for camera {camera_config.name}."
204+
)
204205

205206

206207
def verify_recording_retention(camera_config: CameraConfig) -> None:
@@ -579,15 +580,15 @@ def post_validation(self, info: ValidationInfo) -> Self:
579580
zone.generate_contour(camera_config.frame_shape)
580581

581582
# Set live view stream if none is set
582-
if not camera_config.live.stream_name:
583-
camera_config.live.stream_name = name
583+
if not camera_config.live.streams:
584+
camera_config.live.streams = {name: name}
584585

585586
# generate the ffmpeg commands
586587
camera_config.create_ffmpeg_cmds()
587588
self.cameras[name] = camera_config
588589

589590
verify_config_roles(camera_config)
590-
verify_valid_live_stream_name(self, camera_config)
591+
verify_valid_live_stream_names(self, camera_config)
591592
verify_recording_retention(camera_config)
592593
verify_recording_segments_setup_with_reasonable_time(camera_config)
593594
verify_zone_objects_are_tracked(camera_config)

frigate/events/audio.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import numpy as np
1010
import requests
1111

12+
import frigate.util as util
1213
from frigate.camera import CameraMetrics
1314
from frigate.comms.config_updater import ConfigSubscriber
1415
from frigate.comms.detections_updater import DetectionPublisher, DetectionTypeEnum
@@ -25,7 +26,6 @@
2526
from frigate.ffmpeg_presets import parse_preset_input
2627
from frigate.log import LogPipe
2728
from frigate.object_detection import load_labels
28-
from frigate.service_manager import ServiceProcess
2929
from frigate.util.builtin import get_ffmpeg_arg_list
3030
from frigate.video import start_or_restart_ffmpeg, stop_ffmpeg
3131

@@ -63,15 +63,15 @@ def get_ffmpeg_command(ffmpeg: FfmpegConfig) -> list[str]:
6363
)
6464

6565

66-
class AudioProcessor(ServiceProcess):
66+
class AudioProcessor(util.Process):
6767
name = "frigate.audio_manager"
6868

6969
def __init__(
7070
self,
7171
cameras: list[CameraConfig],
7272
camera_metrics: dict[str, CameraMetrics],
7373
):
74-
super().__init__()
74+
super().__init__(name="frigate.audio_manager", daemon=True)
7575

7676
self.camera_metrics = camera_metrics
7777
self.cameras = cameras

frigate/genai/__init__.py

+13-6
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from playhouse.shortcuts import model_to_dict
88

9-
from frigate.config import CameraConfig, GenAIConfig, GenAIProviderEnum
9+
from frigate.config import CameraConfig, FrigateConfig, GenAIConfig, GenAIProviderEnum
1010
from frigate.models import Event
1111

1212
PROVIDERS = {}
@@ -52,12 +52,19 @@ def _send(self, prompt: str, images: list[bytes]) -> Optional[str]:
5252
return None
5353

5454

55-
def get_genai_client(genai_config: GenAIConfig) -> Optional[GenAIClient]:
55+
def get_genai_client(config: FrigateConfig) -> Optional[GenAIClient]:
5656
"""Get the GenAI client."""
57-
load_providers()
58-
provider = PROVIDERS.get(genai_config.provider)
59-
if provider:
60-
return provider(genai_config)
57+
genai_config = config.genai
58+
genai_cameras = [
59+
c for c in config.cameras.values() if c.enabled and c.genai.enabled
60+
]
61+
62+
if genai_cameras:
63+
load_providers()
64+
provider = PROVIDERS.get(genai_config.provider)
65+
if provider:
66+
return provider(genai_config)
67+
6168
return None
6269

6370

frigate/util/config.py

+31-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
logger = logging.getLogger(__name__)
1515

16-
CURRENT_CONFIG_VERSION = "0.15-0"
16+
CURRENT_CONFIG_VERSION = "0.16-0"
1717

1818

1919
def migrate_frigate_config(config_file: str):
@@ -67,6 +67,13 @@ def migrate_frigate_config(config_file: str):
6767
yaml.dump(new_config, f)
6868
previous_version = "0.15-0"
6969

70+
if previous_version < "0.16-0":
71+
logger.info(f"Migrating frigate config from {previous_version} to 0.16-0...")
72+
new_config = migrate_016_0(config)
73+
with open(config_file, "w") as f:
74+
yaml.dump(new_config, f)
75+
previous_version = "0.16-0"
76+
7077
logger.info("Finished frigate config migration...")
7178

7279

@@ -257,6 +264,29 @@ def migrate_015_0(config: dict[str, dict[str, any]]) -> dict[str, dict[str, any]
257264
return new_config
258265

259266

267+
def migrate_016_0(config: dict[str, dict[str, any]]) -> dict[str, dict[str, any]]:
268+
"""Handle migrating frigate config to 0.16-0"""
269+
new_config = config.copy()
270+
271+
for name, camera in config.get("cameras", {}).items():
272+
camera_config: dict[str, dict[str, any]] = camera.copy()
273+
274+
live_config = camera_config.get("live", {})
275+
if "stream_name" in live_config:
276+
# Migrate from live -> stream_name to live -> streams -> dict
277+
stream_name = live_config["stream_name"]
278+
live_config["streams"] = {stream_name: stream_name}
279+
280+
del live_config["stream_name"]
281+
282+
camera_config["live"] = live_config
283+
284+
new_config["cameras"][name] = camera_config
285+
286+
new_config["version"] = "0.16-0"
287+
return new_config
288+
289+
260290
def get_relative_coordinates(
261291
mask: Optional[Union[str, list]], frame_shape: tuple[int, int]
262292
) -> Union[str, list]:

frigate/util/model.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,12 @@ def get_ort_providers(
3333

3434
for provider in ort.get_available_providers():
3535
if provider == "CUDAExecutionProvider":
36+
device_id = 0 if not device.isdigit() else int(device)
3637
providers.append(provider)
3738
options.append(
3839
{
3940
"arena_extend_strategy": "kSameAsRequested",
41+
"device_id": device_id,
4042
}
4143
)
4244
elif provider == "TensorrtExecutionProvider":
@@ -46,10 +48,11 @@ def get_ort_providers(
4648
os.makedirs(
4749
"/config/model_cache/tensorrt/ort/trt-engines", exist_ok=True
4850
)
51+
device_id = 0 if not device.isdigit() else int(device)
4952
providers.append(provider)
5053
options.append(
5154
{
52-
"arena_extend_strategy": "kSameAsRequested",
55+
"device_id": device_id,
5356
"trt_fp16_enable": requires_fp16
5457
and os.environ.get("USE_FP_16", "True") != "False",
5558
"trt_timing_cache_enable": True,

0 commit comments

Comments
 (0)