Rewrite Strix from scratch as single binary
Complete architecture rewrite following go2rtc patterns: - pkg/ for pure logic (camdb, tester, probe, generate) - internal/ for application glue with Init() modules - Single HTTP server on :4567 with all endpoints - zerolog with password masking and memory ring buffer - Environment-based config only (no YAML files) API endpoints: /api/search, /api/streams, /api/test, /api/probe, /api/generate, /api/health, /api/log Dependencies: go2rtc v1.9.14, go-sqlite3, miekg/dns, zerolog
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
package generate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var needMP4 = map[string]bool{"bubble": true}
|
||||
|
||||
var reIPv4 = regexp.MustCompile(`\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}`)
|
||||
|
||||
func Generate(req *Request) (*Response, error) {
|
||||
if req.MainStream == "" {
|
||||
return nil, fmt.Errorf("generate: mainStream required")
|
||||
}
|
||||
|
||||
info := buildInfo(req)
|
||||
|
||||
if len(req.Objects) > 0 && (req.Detect == nil || !req.Detect.Enabled) {
|
||||
if req.Detect == nil {
|
||||
req.Detect = &DetectConfig{Enabled: true}
|
||||
} else {
|
||||
req.Detect.Enabled = true
|
||||
}
|
||||
}
|
||||
|
||||
if strings.TrimSpace(req.ExistingConfig) == "" {
|
||||
config := newConfig(info, req)
|
||||
return &Response{Config: config, Diff: fullDiff(config)}, nil
|
||||
}
|
||||
|
||||
return addToConfig(req.ExistingConfig, info, req)
|
||||
}
|
||||
|
||||
func buildInfo(req *Request) *cameraInfo {
|
||||
mainScheme := urlScheme(req.MainStream)
|
||||
ip := extractIP(req.MainStream)
|
||||
sanitized := strings.NewReplacer(".", "_", ":", "_").Replace(ip)
|
||||
|
||||
base := "camera"
|
||||
streamBase := "stream"
|
||||
if ip != "" {
|
||||
base = "camera_" + sanitized
|
||||
streamBase = sanitized
|
||||
}
|
||||
|
||||
info := &cameraInfo{
|
||||
CameraName: base,
|
||||
MainStreamName: streamBase + "_main",
|
||||
MainSource: req.MainStream,
|
||||
}
|
||||
|
||||
if req.Name != "" {
|
||||
info.CameraName = req.Name
|
||||
info.MainStreamName = req.Name + "_main"
|
||||
}
|
||||
|
||||
if req.Go2RTC != nil {
|
||||
if req.Go2RTC.MainStreamName != "" {
|
||||
info.MainStreamName = req.Go2RTC.MainStreamName
|
||||
}
|
||||
if req.Go2RTC.MainStreamSource != "" {
|
||||
info.MainSource = req.Go2RTC.MainStreamSource
|
||||
}
|
||||
}
|
||||
|
||||
info.MainPath = "rtsp://127.0.0.1:8554/" + info.MainStreamName
|
||||
if needMP4[mainScheme] {
|
||||
info.MainPath += "?mp4"
|
||||
}
|
||||
info.MainInputArgs = "preset-rtsp-restream"
|
||||
|
||||
if req.Frigate != nil {
|
||||
if req.Frigate.MainStreamPath != "" {
|
||||
info.MainPath = req.Frigate.MainStreamPath
|
||||
}
|
||||
if req.Frigate.MainStreamInputArgs != "" {
|
||||
info.MainInputArgs = req.Frigate.MainStreamInputArgs
|
||||
}
|
||||
}
|
||||
|
||||
if req.SubStream != "" {
|
||||
subScheme := urlScheme(req.SubStream)
|
||||
subName := streamBase + "_sub"
|
||||
if req.Name != "" {
|
||||
subName = req.Name + "_sub"
|
||||
}
|
||||
subSource := req.SubStream
|
||||
subPath := "rtsp://127.0.0.1:8554/" + subName
|
||||
if needMP4[subScheme] {
|
||||
subPath += "?mp4"
|
||||
}
|
||||
subInputArgs := "preset-rtsp-restream"
|
||||
|
||||
if req.Go2RTC != nil {
|
||||
if req.Go2RTC.SubStreamName != "" {
|
||||
subName = req.Go2RTC.SubStreamName
|
||||
}
|
||||
if req.Go2RTC.SubStreamSource != "" {
|
||||
subSource = req.Go2RTC.SubStreamSource
|
||||
}
|
||||
}
|
||||
if req.Frigate != nil {
|
||||
if req.Frigate.SubStreamPath != "" {
|
||||
subPath = req.Frigate.SubStreamPath
|
||||
}
|
||||
if req.Frigate.SubStreamInputArgs != "" {
|
||||
subInputArgs = req.Frigate.SubStreamInputArgs
|
||||
}
|
||||
}
|
||||
|
||||
info.SubStreamName = subName
|
||||
info.SubSource = subSource
|
||||
info.SubPath = subPath
|
||||
info.SubInputArgs = subInputArgs
|
||||
}
|
||||
|
||||
return info
|
||||
}
|
||||
|
||||
func newConfig(info *cameraInfo, req *Request) string {
|
||||
var b strings.Builder
|
||||
|
||||
b.WriteString("mqtt:\n enabled: false\n\n")
|
||||
b.WriteString("record:\n enabled: true\n retain:\n days: 7\n mode: motion\n\n")
|
||||
|
||||
b.WriteString("go2rtc:\n streams:\n")
|
||||
writeStreamLines(&b, info)
|
||||
|
||||
b.WriteString("cameras:\n")
|
||||
writeCameraBlock(&b, info, req)
|
||||
|
||||
b.WriteString("version: 0.18-0\n")
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// internals
|
||||
|
||||
type cameraInfo struct {
|
||||
CameraName string
|
||||
MainStreamName string
|
||||
MainSource string
|
||||
MainPath string
|
||||
MainInputArgs string
|
||||
SubStreamName string
|
||||
SubSource string
|
||||
SubPath string
|
||||
SubInputArgs string
|
||||
}
|
||||
|
||||
func urlScheme(rawURL string) string {
|
||||
if i := strings.IndexByte(rawURL, ':'); i > 0 {
|
||||
return rawURL[:i]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func extractIP(rawURL string) string {
|
||||
if u, err := url.Parse(rawURL); err == nil && u.Hostname() != "" {
|
||||
return u.Hostname()
|
||||
}
|
||||
if m := reIPv4.FindString(rawURL); m != "" {
|
||||
return m
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package generate
|
||||
|
||||
import "strings"
|
||||
|
||||
func fullDiff(config string) []DiffLine {
|
||||
lines := strings.Split(config, "\n")
|
||||
diff := make([]DiffLine, len(lines))
|
||||
for i, line := range lines {
|
||||
diff[i] = DiffLine{Line: i + 1, Text: line, Type: "added"}
|
||||
}
|
||||
return diff
|
||||
}
|
||||
|
||||
func diffWithContext(lines []string, added map[int]bool, ctx int) []DiffLine {
|
||||
visible := make(map[int]bool)
|
||||
for idx := range added {
|
||||
for c := -ctx; c <= ctx; c++ {
|
||||
if j := idx + c; j >= 0 && j < len(lines) {
|
||||
visible[j] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var diff []DiffLine
|
||||
for i, line := range lines {
|
||||
if !visible[i] {
|
||||
continue
|
||||
}
|
||||
t := "context"
|
||||
if added[i] {
|
||||
t = "added"
|
||||
}
|
||||
diff = append(diff, DiffLine{Line: i + 1, Text: line, Type: t})
|
||||
}
|
||||
return diff
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
package generate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
reCamerasHeader = regexp.MustCompile(`^cameras:`)
|
||||
reTopLevel = regexp.MustCompile(`^[a-z]`)
|
||||
reCameraName = regexp.MustCompile(`^\s{2}(\w[\w-]*):`)
|
||||
reStreamsHeader = regexp.MustCompile(`^\s{2}streams:`)
|
||||
reStreamName = regexp.MustCompile(`^\s{4}'?(\w[\w-]*)'?:`)
|
||||
reStreamContent = regexp.MustCompile(`^\s{4,}`)
|
||||
reNextSection = regexp.MustCompile(`^[a-z#]`)
|
||||
reCameraBody = regexp.MustCompile(`^\s{2,}\S`)
|
||||
reVersion = regexp.MustCompile(`^version:`)
|
||||
)
|
||||
|
||||
func addToConfig(existing string, info *cameraInfo, req *Request) (*Response, error) {
|
||||
lines := strings.Split(existing, "\n")
|
||||
|
||||
existingCams := findNames(lines, reCamerasHeader, reCameraName)
|
||||
existingStreams := findNames(lines, reStreamsHeader, reStreamName)
|
||||
|
||||
info = dedup(info, existingCams, existingStreams)
|
||||
|
||||
streamIdx := findStreamInsertPoint(lines)
|
||||
cameraIdx := findCameraInsertPoint(lines)
|
||||
|
||||
if streamIdx == -1 || cameraIdx == -1 {
|
||||
return nil, fmt.Errorf("generate: can't find go2rtc streams or cameras section")
|
||||
}
|
||||
|
||||
var sb strings.Builder
|
||||
writeStreamLines(&sb, info)
|
||||
streamLines := strings.Split(strings.TrimRight(sb.String(), "\n"), "\n")
|
||||
|
||||
sb.Reset()
|
||||
writeCameraBlock(&sb, info, req)
|
||||
cameraLines := strings.Split(strings.TrimRight(sb.String(), "\n"), "\n")
|
||||
|
||||
added := make(map[int]bool)
|
||||
result := make([]string, 0, len(lines)+len(streamLines)+len(cameraLines))
|
||||
|
||||
result = append(result, lines[:streamIdx]...)
|
||||
mark := len(result)
|
||||
result = append(result, streamLines...)
|
||||
for i := range streamLines {
|
||||
added[mark+i] = true
|
||||
}
|
||||
|
||||
shift := len(streamLines)
|
||||
adjCameraIdx := cameraIdx + shift
|
||||
rest := lines[streamIdx:]
|
||||
split := adjCameraIdx - len(result)
|
||||
|
||||
result = append(result, rest[:split]...)
|
||||
mark = len(result)
|
||||
result = append(result, cameraLines...)
|
||||
for i := range cameraLines {
|
||||
added[mark+i] = true
|
||||
}
|
||||
result = append(result, rest[split:]...)
|
||||
|
||||
config := strings.Join(result, "\n")
|
||||
diff := diffWithContext(result, added, 3)
|
||||
return &Response{Config: config, Diff: diff}, nil
|
||||
}
|
||||
|
||||
func dedup(info *cameraInfo, cams, streams map[string]bool) *cameraInfo {
|
||||
out := *info
|
||||
|
||||
suffix := 0
|
||||
base := out.CameraName
|
||||
for cams[out.CameraName] {
|
||||
suffix++
|
||||
out.CameraName = fmt.Sprintf("%s_%d", base, suffix)
|
||||
}
|
||||
|
||||
base = out.MainStreamName
|
||||
for streams[out.MainStreamName] {
|
||||
suffix++
|
||||
out.MainStreamName = fmt.Sprintf("%s_%d", base, suffix)
|
||||
}
|
||||
|
||||
if out.SubStreamName != "" {
|
||||
base = out.SubStreamName
|
||||
for streams[out.SubStreamName] {
|
||||
suffix++
|
||||
out.SubStreamName = fmt.Sprintf("%s_%d", base, suffix)
|
||||
}
|
||||
}
|
||||
|
||||
return &out
|
||||
}
|
||||
|
||||
func findNames(lines []string, header, nameRe *regexp.Regexp) map[string]bool {
|
||||
names := make(map[string]bool)
|
||||
in := false
|
||||
for _, line := range lines {
|
||||
if header.MatchString(line) {
|
||||
in = true
|
||||
continue
|
||||
}
|
||||
if in && reTopLevel.MatchString(line) {
|
||||
break
|
||||
}
|
||||
if in {
|
||||
if m := nameRe.FindStringSubmatch(line); m != nil {
|
||||
names[m[1]] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
func findStreamInsertPoint(lines []string) int {
|
||||
in := false
|
||||
last := -1
|
||||
headerIdx := -1
|
||||
for i, line := range lines {
|
||||
if reStreamsHeader.MatchString(line) {
|
||||
in = true
|
||||
headerIdx = i
|
||||
continue
|
||||
}
|
||||
if in {
|
||||
if reStreamContent.MatchString(line) {
|
||||
last = i
|
||||
} else if reNextSection.MatchString(line) {
|
||||
if last >= 0 && last+1 < len(lines) && strings.TrimSpace(lines[last+1]) == "" {
|
||||
return last + 2
|
||||
}
|
||||
if last >= 0 {
|
||||
return last + 1
|
||||
}
|
||||
return headerIdx + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
if last >= 0 {
|
||||
return last + 1
|
||||
}
|
||||
if headerIdx >= 0 {
|
||||
return headerIdx + 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func findCameraInsertPoint(lines []string) int {
|
||||
in := false
|
||||
last := -1
|
||||
headerIdx := -1
|
||||
for i, line := range lines {
|
||||
if reCamerasHeader.MatchString(line) {
|
||||
in = true
|
||||
headerIdx = i
|
||||
continue
|
||||
}
|
||||
if in {
|
||||
if reCameraBody.MatchString(line) {
|
||||
last = i
|
||||
} else if reTopLevel.MatchString(line) && !reCamerasHeader.MatchString(line) {
|
||||
if last < 0 {
|
||||
return headerIdx + 1
|
||||
}
|
||||
idx := last + 1
|
||||
for idx < len(lines) && strings.TrimSpace(lines[idx]) == "" {
|
||||
idx++
|
||||
}
|
||||
return idx
|
||||
} else if reVersion.MatchString(line) {
|
||||
if last < 0 {
|
||||
return headerIdx + 1
|
||||
}
|
||||
idx := i
|
||||
for idx > 0 && strings.TrimSpace(lines[idx-1]) == "" {
|
||||
idx--
|
||||
}
|
||||
return idx
|
||||
}
|
||||
}
|
||||
}
|
||||
if headerIdx >= 0 {
|
||||
return headerIdx + 1
|
||||
}
|
||||
return len(lines)
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package generate
|
||||
|
||||
type Request struct {
|
||||
MainStream string `json:"mainStream"`
|
||||
SubStream string `json:"subStream,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
ExistingConfig string `json:"existingConfig,omitempty"`
|
||||
|
||||
Go2RTC *Go2RTCOverride `json:"go2rtc,omitempty"`
|
||||
Frigate *FrigateOverride `json:"frigate,omitempty"`
|
||||
|
||||
Objects []string `json:"objects,omitempty"`
|
||||
Record *RecordConfig `json:"record,omitempty"`
|
||||
Detect *DetectConfig `json:"detect,omitempty"`
|
||||
Snapshots *BoolConfig `json:"snapshots,omitempty"`
|
||||
Motion *MotionConfig `json:"motion,omitempty"`
|
||||
|
||||
FFmpeg *FFmpegConfig `json:"ffmpeg,omitempty"`
|
||||
Live *LiveConfig `json:"live,omitempty"`
|
||||
Audio *AudioConfig `json:"audio,omitempty"`
|
||||
Birdseye *BirdseyeConfig `json:"birdseye,omitempty"`
|
||||
ONVIF *ONVIFConfig `json:"onvif,omitempty"`
|
||||
PTZ *PTZConfig `json:"ptz,omitempty"`
|
||||
Notifications *BoolConfig `json:"notifications,omitempty"`
|
||||
UI *UIConfig `json:"ui,omitempty"`
|
||||
}
|
||||
|
||||
type Go2RTCOverride struct {
|
||||
MainStreamName string `json:"mainStreamName,omitempty"`
|
||||
SubStreamName string `json:"subStreamName,omitempty"`
|
||||
MainStreamSource string `json:"mainStreamSource,omitempty"`
|
||||
SubStreamSource string `json:"subStreamSource,omitempty"`
|
||||
}
|
||||
|
||||
type FrigateOverride struct {
|
||||
MainStreamPath string `json:"mainStreamPath,omitempty"`
|
||||
SubStreamPath string `json:"subStreamPath,omitempty"`
|
||||
MainStreamInputArgs string `json:"mainStreamInputArgs,omitempty"`
|
||||
SubStreamInputArgs string `json:"subStreamInputArgs,omitempty"`
|
||||
}
|
||||
|
||||
type RecordConfig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
RetainDays float64 `json:"retain_days,omitempty"`
|
||||
Mode string `json:"mode,omitempty"`
|
||||
AlertsDays float64 `json:"alerts_days,omitempty"`
|
||||
DetectionDays float64 `json:"detections_days,omitempty"`
|
||||
PreCapture int `json:"pre_capture,omitempty"`
|
||||
PostCapture int `json:"post_capture,omitempty"`
|
||||
}
|
||||
|
||||
type DetectConfig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
FPS int `json:"fps,omitempty"`
|
||||
Width int `json:"width,omitempty"`
|
||||
Height int `json:"height,omitempty"`
|
||||
}
|
||||
|
||||
type MotionConfig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Threshold int `json:"threshold,omitempty"`
|
||||
ContourArea int `json:"contour_area,omitempty"`
|
||||
}
|
||||
|
||||
type FFmpegConfig struct {
|
||||
HWAccel string `json:"hwaccel,omitempty"`
|
||||
GPU int `json:"gpu,omitempty"`
|
||||
}
|
||||
|
||||
type LiveConfig struct {
|
||||
Height int `json:"height,omitempty"`
|
||||
Quality int `json:"quality,omitempty"`
|
||||
}
|
||||
|
||||
type AudioConfig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Filters []string `json:"filters,omitempty"`
|
||||
}
|
||||
|
||||
type BirdseyeConfig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Mode string `json:"mode,omitempty"`
|
||||
}
|
||||
|
||||
type ONVIFConfig struct {
|
||||
Host string `json:"host,omitempty"`
|
||||
Port int `json:"port,omitempty"`
|
||||
User string `json:"user,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
AutoTracking bool `json:"autotracking,omitempty"`
|
||||
RequiredZones []string `json:"required_zones,omitempty"`
|
||||
}
|
||||
|
||||
type PTZConfig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Presets map[string]string `json:"presets,omitempty"`
|
||||
}
|
||||
|
||||
type BoolConfig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
type UIConfig struct {
|
||||
Order int `json:"order,omitempty"`
|
||||
Dashboard bool `json:"dashboard"`
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Config string `json:"config"`
|
||||
Diff []DiffLine `json:"diff"`
|
||||
}
|
||||
|
||||
type DiffLine struct {
|
||||
Line int `json:"line"`
|
||||
Text string `json:"text"`
|
||||
Type string `json:"type"` // context, added, removed
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
package generate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func writeStreamLines(b *strings.Builder, info *cameraInfo) {
|
||||
fmt.Fprintf(b, " '%s':\n", info.MainStreamName)
|
||||
fmt.Fprintf(b, " - %s\n", info.MainSource)
|
||||
|
||||
if info.SubStreamName != "" {
|
||||
fmt.Fprintf(b, " '%s':\n", info.SubStreamName)
|
||||
fmt.Fprintf(b, " - %s\n", info.SubSource)
|
||||
}
|
||||
|
||||
b.WriteByte('\n')
|
||||
}
|
||||
|
||||
func writeCameraBlock(b *strings.Builder, info *cameraInfo, req *Request) {
|
||||
fmt.Fprintf(b, " %s:\n", info.CameraName)
|
||||
|
||||
b.WriteString(" ffmpeg:\n")
|
||||
writeFFmpegGlobal(b, req)
|
||||
b.WriteString(" inputs:\n")
|
||||
|
||||
if info.SubStreamName != "" {
|
||||
writeInput(b, info.SubPath, info.SubInputArgs, "detect")
|
||||
writeInput(b, info.MainPath, info.MainInputArgs, "record")
|
||||
} else {
|
||||
writeInput(b, info.MainPath, info.MainInputArgs, "detect", "record")
|
||||
}
|
||||
|
||||
writeLive(b, info, req)
|
||||
writeDetect(b, req)
|
||||
writeObjects(b, req)
|
||||
writeMotion(b, req)
|
||||
writeRecord(b, req)
|
||||
writeSnapshots(b, req)
|
||||
writeAudio(b, req)
|
||||
writeBirdseye(b, req)
|
||||
writeONVIF(b, req)
|
||||
writeNotifications(b, req)
|
||||
writeUI(b, req)
|
||||
|
||||
b.WriteByte('\n')
|
||||
}
|
||||
|
||||
func writeInput(b *strings.Builder, path, inputArgs string, roles ...string) {
|
||||
fmt.Fprintf(b, " - path: %s\n", path)
|
||||
fmt.Fprintf(b, " input_args: %s\n", inputArgs)
|
||||
b.WriteString(" roles:\n")
|
||||
for _, r := range roles {
|
||||
fmt.Fprintf(b, " - %s\n", r)
|
||||
}
|
||||
}
|
||||
|
||||
func writeFFmpegGlobal(b *strings.Builder, req *Request) {
|
||||
if req.FFmpeg == nil {
|
||||
return
|
||||
}
|
||||
if req.FFmpeg.HWAccel != "" && req.FFmpeg.HWAccel != "auto" {
|
||||
fmt.Fprintf(b, " hwaccel_args: %s\n", req.FFmpeg.HWAccel)
|
||||
}
|
||||
if req.FFmpeg.GPU > 0 {
|
||||
fmt.Fprintf(b, " gpu: %d\n", req.FFmpeg.GPU)
|
||||
}
|
||||
}
|
||||
|
||||
func writeLive(b *strings.Builder, info *cameraInfo, req *Request) {
|
||||
if info.SubStreamName == "" && req.Live == nil {
|
||||
return
|
||||
}
|
||||
|
||||
b.WriteString(" live:\n")
|
||||
|
||||
if info.SubStreamName != "" {
|
||||
b.WriteString(" streams:\n")
|
||||
fmt.Fprintf(b, " Main Stream: %s\n", info.MainStreamName)
|
||||
fmt.Fprintf(b, " Sub Stream: %s\n", info.SubStreamName)
|
||||
}
|
||||
|
||||
if req.Live != nil {
|
||||
if req.Live.Height > 0 {
|
||||
fmt.Fprintf(b, " height: %d\n", req.Live.Height)
|
||||
}
|
||||
if req.Live.Quality > 0 {
|
||||
fmt.Fprintf(b, " quality: %d\n", req.Live.Quality)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func writeDetect(b *strings.Builder, req *Request) {
|
||||
if req.Detect == nil {
|
||||
b.WriteString(" detect:\n enabled: true\n")
|
||||
return
|
||||
}
|
||||
|
||||
b.WriteString(" detect:\n")
|
||||
fmt.Fprintf(b, " enabled: %t\n", req.Detect.Enabled)
|
||||
if req.Detect.FPS > 0 {
|
||||
fmt.Fprintf(b, " fps: %d\n", req.Detect.FPS)
|
||||
}
|
||||
if req.Detect.Width > 0 {
|
||||
fmt.Fprintf(b, " width: %d\n", req.Detect.Width)
|
||||
}
|
||||
if req.Detect.Height > 0 {
|
||||
fmt.Fprintf(b, " height: %d\n", req.Detect.Height)
|
||||
}
|
||||
}
|
||||
|
||||
func writeObjects(b *strings.Builder, req *Request) {
|
||||
objects := req.Objects
|
||||
if len(objects) == 0 {
|
||||
objects = []string{"person"}
|
||||
}
|
||||
|
||||
b.WriteString(" objects:\n track:\n")
|
||||
for _, obj := range objects {
|
||||
fmt.Fprintf(b, " - %s\n", obj)
|
||||
}
|
||||
}
|
||||
|
||||
func writeMotion(b *strings.Builder, req *Request) {
|
||||
if req.Motion == nil {
|
||||
return
|
||||
}
|
||||
|
||||
b.WriteString(" motion:\n")
|
||||
fmt.Fprintf(b, " enabled: %t\n", req.Motion.Enabled)
|
||||
if req.Motion.Threshold > 0 {
|
||||
fmt.Fprintf(b, " threshold: %d\n", req.Motion.Threshold)
|
||||
}
|
||||
if req.Motion.ContourArea > 0 {
|
||||
fmt.Fprintf(b, " contour_area: %d\n", req.Motion.ContourArea)
|
||||
}
|
||||
}
|
||||
|
||||
func writeRecord(b *strings.Builder, req *Request) {
|
||||
if req.Record == nil {
|
||||
b.WriteString(" record:\n enabled: true\n")
|
||||
return
|
||||
}
|
||||
|
||||
b.WriteString(" record:\n")
|
||||
fmt.Fprintf(b, " enabled: %t\n", req.Record.Enabled)
|
||||
|
||||
if req.Record.RetainDays > 0 || req.Record.Mode != "" {
|
||||
b.WriteString(" retain:\n")
|
||||
if req.Record.RetainDays > 0 {
|
||||
fmt.Fprintf(b, " days: %g\n", req.Record.RetainDays)
|
||||
}
|
||||
if req.Record.Mode != "" {
|
||||
fmt.Fprintf(b, " mode: %s\n", req.Record.Mode)
|
||||
}
|
||||
}
|
||||
|
||||
if req.Record.AlertsDays > 0 || req.Record.PreCapture > 0 || req.Record.PostCapture > 0 {
|
||||
b.WriteString(" alerts:\n")
|
||||
if req.Record.AlertsDays > 0 {
|
||||
fmt.Fprintf(b, " retain:\n days: %g\n", req.Record.AlertsDays)
|
||||
}
|
||||
if req.Record.PreCapture > 0 {
|
||||
fmt.Fprintf(b, " pre_capture: %d\n", req.Record.PreCapture)
|
||||
}
|
||||
if req.Record.PostCapture > 0 {
|
||||
fmt.Fprintf(b, " post_capture: %d\n", req.Record.PostCapture)
|
||||
}
|
||||
}
|
||||
|
||||
if req.Record.DetectionDays > 0 {
|
||||
fmt.Fprintf(b, " detections:\n retain:\n days: %g\n", req.Record.DetectionDays)
|
||||
}
|
||||
}
|
||||
|
||||
func writeSnapshots(b *strings.Builder, req *Request) {
|
||||
if req.Snapshots == nil || !req.Snapshots.Enabled {
|
||||
return
|
||||
}
|
||||
b.WriteString(" snapshots:\n enabled: true\n")
|
||||
}
|
||||
|
||||
func writeAudio(b *strings.Builder, req *Request) {
|
||||
if req.Audio == nil || !req.Audio.Enabled {
|
||||
return
|
||||
}
|
||||
|
||||
b.WriteString(" audio:\n enabled: true\n")
|
||||
if len(req.Audio.Filters) > 0 {
|
||||
b.WriteString(" filters:\n")
|
||||
for _, f := range req.Audio.Filters {
|
||||
fmt.Fprintf(b, " - %s\n", f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func writeBirdseye(b *strings.Builder, req *Request) {
|
||||
if req.Birdseye == nil {
|
||||
return
|
||||
}
|
||||
|
||||
b.WriteString(" birdseye:\n")
|
||||
fmt.Fprintf(b, " enabled: %t\n", req.Birdseye.Enabled)
|
||||
if req.Birdseye.Mode != "" {
|
||||
fmt.Fprintf(b, " mode: %s\n", req.Birdseye.Mode)
|
||||
}
|
||||
}
|
||||
|
||||
func writeONVIF(b *strings.Builder, req *Request) {
|
||||
if req.ONVIF == nil || req.ONVIF.Host == "" {
|
||||
return
|
||||
}
|
||||
|
||||
b.WriteString(" onvif:\n")
|
||||
fmt.Fprintf(b, " host: %s\n", req.ONVIF.Host)
|
||||
|
||||
port := req.ONVIF.Port
|
||||
if port == 0 {
|
||||
port = 80
|
||||
}
|
||||
fmt.Fprintf(b, " port: %d\n", port)
|
||||
|
||||
if req.ONVIF.User != "" {
|
||||
fmt.Fprintf(b, " user: %s\n", req.ONVIF.User)
|
||||
fmt.Fprintf(b, " password: %s\n", req.ONVIF.Password)
|
||||
}
|
||||
|
||||
if req.ONVIF.AutoTracking {
|
||||
b.WriteString(" autotracking:\n enabled: true\n")
|
||||
if len(req.ONVIF.RequiredZones) > 0 {
|
||||
b.WriteString(" required_zones:\n")
|
||||
for _, z := range req.ONVIF.RequiredZones {
|
||||
fmt.Fprintf(b, " - %s\n", z)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if req.PTZ != nil && len(req.PTZ.Presets) > 0 {
|
||||
b.WriteString(" ptz:\n presets:\n")
|
||||
for name, token := range req.PTZ.Presets {
|
||||
fmt.Fprintf(b, " %s: %s\n", name, token)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func writeNotifications(b *strings.Builder, req *Request) {
|
||||
if req.Notifications == nil || !req.Notifications.Enabled {
|
||||
return
|
||||
}
|
||||
b.WriteString(" notifications:\n enabled: true\n")
|
||||
}
|
||||
|
||||
func writeUI(b *strings.Builder, req *Request) {
|
||||
if req.UI == nil {
|
||||
return
|
||||
}
|
||||
|
||||
b.WriteString(" ui:\n")
|
||||
if req.UI.Order > 0 {
|
||||
fmt.Fprintf(b, " order: %d\n", req.UI.Order)
|
||||
}
|
||||
if !req.UI.Dashboard {
|
||||
b.WriteString(" dashboard: false\n")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user