Merged in nicer-update-func (pull request #411)

revid/config: variables struct providing names, types, update and validation

Approved-by: Trek Hopton <trek.hopton@gmail.com>
This commit is contained in:
Saxon Milton 2020-05-19 06:02:02 +00:00
parent a286d6c140
commit ce50b5fbd1
8 changed files with 778 additions and 531 deletions

View File

@ -136,7 +136,7 @@ func main() {
var rv *revid.Revid var rv *revid.Revid
log.Log(logger.Debug, "initialising netsender client") log.Log(logger.Debug, "initialising netsender client")
ns, err := netsender.New(log, nil, readPin(rv), nil, config.TypeData) ns, err := netsender.New(log, nil, readPin(rv), nil, createVarMap())
if err != nil { if err != nil {
log.Log(logger.Fatal, pkg+"could not initialise netsender client: "+err.Error()) log.Log(logger.Fatal, pkg+"could not initialise netsender client: "+err.Error())
} }
@ -235,6 +235,14 @@ func run(rv *revid.Revid, ns *netsender.Sender, l *logger.Logger, nl *netlogger.
} }
} }
func createVarMap() map[string]string {
var m map[string]string
for _, v := range config.Variables {
m[v.Name] = v.Type_
}
return m
}
// profile opens a file to hold CPU profiling metrics and then starts the // profile opens a file to hold CPU profiling metrics and then starts the
// CPU profiler. // CPU profiler.
func profile(l *logger.Logger) { func profile(l *logger.Logger) {

View File

@ -41,7 +41,7 @@ type VariableFPS struct {
} }
// NewVariableFPS returns a pointer to a new VariableFPS filter struct. // NewVariableFPS returns a pointer to a new VariableFPS filter struct.
func NewVariableFPS(dst io.WriteCloser, minFPS float64, filter Filter) *VariableFPS { func NewVariableFPS(dst io.WriteCloser, minFPS uint, filter Filter) *VariableFPS {
frames := uint(25 / minFPS) frames := uint(25 / minFPS)
return &VariableFPS{filter, dst, frames, 0} return &VariableFPS{filter, dst, frames, 0}
} }

1
go.mod
View File

@ -8,6 +8,7 @@ require (
github.com/Comcast/gots v0.0.0-20190305015453-8d56e473f0f7 github.com/Comcast/gots v0.0.0-20190305015453-8d56e473f0f7
github.com/go-audio/audio v0.0.0-20181013203223-7b2a6ca21480 github.com/go-audio/audio v0.0.0-20181013203223-7b2a6ca21480
github.com/go-audio/wav v0.0.0-20181013172942-de841e69b884 github.com/go-audio/wav v0.0.0-20181013172942-de841e69b884
github.com/google/go-cmp v0.4.1
github.com/mewkiz/flac v1.0.5 github.com/mewkiz/flac v1.0.5
github.com/pkg/errors v0.8.1 github.com/pkg/errors v0.8.1
github.com/yobert/alsa v0.0.0-20180630182551-d38d89fa843e github.com/yobert/alsa v0.0.0-20180630182551-d38d89fa843e

3
go.sum
View File

@ -25,6 +25,8 @@ github.com/go-audio/audio v0.0.0-20181013203223-7b2a6ca21480/go.mod h1:6uAu0+H2l
github.com/go-audio/wav v0.0.0-20181013172942-de841e69b884 h1:2TaXIaVA4ff/MHHezOj83tCypALTFAcXOImcFWNa3jw= github.com/go-audio/wav v0.0.0-20181013172942-de841e69b884 h1:2TaXIaVA4ff/MHHezOj83tCypALTFAcXOImcFWNa3jw=
github.com/go-audio/wav v0.0.0-20181013172942-de841e69b884/go.mod h1:UiqzUyfX0zs3pJ/DPyvS5v8sN6s5bXPUDDIVA5v8dks= github.com/go-audio/wav v0.0.0-20181013172942-de841e69b884/go.mod h1:UiqzUyfX0zs3pJ/DPyvS5v8sN6s5bXPUDDIVA5v8dks=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/google/go-cmp v0.4.1 h1:/exdXoGamhu5ONeUJH0deniYLWYvQwW66yvlfiiKTu0=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/jacobsa/go-serial v0.0.0-20180131005756-15cf729a72d4/go.mod h1:2RvX5ZjVtsznNZPEt4xwJXNJrM3VTZoQf7V6gk0ysvs= github.com/jacobsa/go-serial v0.0.0-20180131005756-15cf729a72d4/go.mod h1:2RvX5ZjVtsznNZPEt4xwJXNJrM3VTZoQf7V6gk0ysvs=
github.com/kidoman/embd v0.0.0-20170508013040-d3d8c0c5c68d/go.mod h1:ACKj9jnzOzj1lw2ETilpFGK7L9dtJhAzT7T1OhAGtRQ= github.com/kidoman/embd v0.0.0-20170508013040-d3d8c0c5c68d/go.mod h1:ACKj9jnzOzj1lw2ETilpFGK7L9dtJhAzT7T1OhAGtRQ=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
@ -62,6 +64,7 @@ golang.org/x/image v0.0.0-20200119044424-58c23975cae1 h1:5h3ngYt7+vXCDZCup/HkCQg
golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/sys v0.0.0-20190913121621-c3b328c6e5a7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190913121621-c3b328c6e5a7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=

View File

@ -27,10 +27,8 @@ LICENSE
package config package config
import ( import (
"errors"
"time" "time"
"bitbucket.org/ausocean/av/codec/codecutil"
"bitbucket.org/ausocean/utils/logger" "bitbucket.org/ausocean/utils/logger"
) )
@ -65,33 +63,6 @@ const (
MJPEG MJPEG
) )
// Default config settings
const (
// General revid defaults.
defaultInput = InputRaspivid
defaultOutput = OutputHTTP
defaultTimeout = 0
defaultInputCodec = codecutil.H264
defaultVerbosity = logger.Error
defaultRtpAddr = "localhost:6970"
defaultCameraIP = "192.168.1.50"
defaultBurstPeriod = 10 // Seconds
defaultMinFrames = 100
defaultFrameRate = 25
defaultWriteRate = 25
defaultClipDuration = 0
defaultAudioInputCodec = codecutil.ADPCM
defaultPSITime = 2
defaultFileFPS = 0
// Ring buffer defaults.
defaultRBCapacity = 50000000 // => 50MB
defaultRBWriteTimeout = 5 // Seconds.
// Motion filter parameter defaults.
defaultMinFPS = 1.0
)
// Quality represents video quality. // Quality represents video quality.
type Quality int type Quality int
@ -259,7 +230,7 @@ type Config struct {
HorizontalFlip bool // HorizontalFlip flips video horizontally for Raspivid input. HorizontalFlip bool // HorizontalFlip flips video horizontally for Raspivid input.
VerticalFlip bool // VerticalFlip flips video vertically for Raspivid input. VerticalFlip bool // VerticalFlip flips video vertically for Raspivid input.
Filters []uint8 // Defines the methods of filtering to be used in between lexing and encoding. Filters []uint // Defines the methods of filtering to be used in between lexing and encoding.
PSITime uint // Sets the time between a packet being sent. PSITime uint // Sets the time between a packet being sent.
// Ring buffer parameters. // Ring buffer parameters.
@ -268,7 +239,7 @@ type Config struct {
// Motion filter parameters. // Motion filter parameters.
// Some parameters can be used with any filter, while others can only be used by a few. // Some parameters can be used with any filter, while others can only be used by a few.
MinFPS float64 // The reduced framerate of the video when there is no motion. MinFPS uint // The reduced framerate of the video when there is no motion.
MotionInterval uint // Sets the number of frames that are held before the filter is used (on the nth frame). MotionInterval uint // Sets the number of frames that are held before the filter is used (on the nth frame).
MotionDownscaling uint // Downscaling factor of frames used for motion detection. MotionDownscaling uint // Downscaling factor of frames used for motion detection.
@ -286,187 +257,32 @@ type Config struct {
FileFPS uint FileFPS uint
} }
// TypeData contains information about all of the variables that
// can be set over the web. It is a psuedo const.
var TypeData = map[string]string{
"AutoWhiteBalance": "enum:off,auto,sun,cloud,shade,tungsten,fluorescent,incandescent,flash,horizon",
"BitDepth": "int",
"Bitrate": "uint",
"Brightness": "uint",
"BurstPeriod": "uint",
"CameraChan": "int",
"CameraIP": "string",
"CBR": "bool",
"ClipDuration": "uint",
"Exposure": "enum:auto,night,nightpreview,backlight,spotlight,sports,snow,beach,verylong,fixedfps,antishake,fireworks",
"FileFPS": "int",
"Filters": "enums:NoOp,MOG,VariableFPS,KNN,Difference,Basic",
"FrameRate": "uint",
"Height": "uint",
"HorizontalFlip": "bool",
"HTTPAddress": "string",
"Input": "enum:raspivid,rtsp,v4l,file,audio",
"InputCodec": "enum:H264,MJPEG,PCM,ADPCM",
"InputPath": "string",
"logging": "enum:Debug,Info,Warning,Error,Fatal",
"Loop": "bool",
"MinFPS": "float",
"MinFrames": "uint",
"mode": "enum:Normal,Paused,Burst,Loop",
"MotionDownscaling": "uint",
"MotionHistory": "uint",
"MotionInterval": "int",
"MotionKernel": "uint",
"MotionMinArea": "float",
"MotionPadding": "uint",
"MotionPixels": "int",
"MotionThreshold": "float",
"Output": "enum:File,Http,Rtmp,Rtp",
"OutputPath": "string",
"Outputs": "enums:File,Http,Rtmp,Rtp",
"Quantization": "uint",
"RBCapacity": "uint",
"RBWriteTimeout": "uint",
"Rotation": "uint",
"RTMPURL": "string",
"RTPAddress": "string",
"Saturation": "int",
"VBRBitrate": "int",
"VBRQuality": "enum:standard,fair,good,great,excellent",
"VerticalFlip": "bool",
"Width": "uint",
}
// Validation errors.
var (
errInvalidQuantization = errors.New("invalid quantization")
)
// Validate checks for any errors in the config fields and defaults settings // Validate checks for any errors in the config fields and defaults settings
// if particular parameters have not been defined. // if particular parameters have not been defined.
func (c *Config) Validate() error { func (c *Config) Validate() error {
switch c.LogLevel { for _, v := range Variables {
case logger.Debug, logger.Info, logger.Warning, logger.Error, logger.Fatal: if v.Validate != nil {
default: v.Validate(c)
c.LogInvalidField("LogLevel", defaultVerbosity)
c.LogLevel = defaultVerbosity
}
if c.CameraIP == "" {
c.LogInvalidField("CameraIP", defaultCameraIP)
c.CameraIP = defaultCameraIP
}
switch c.Input {
case InputRaspivid, InputV4L, InputFile, InputAudio, InputRTSP:
case NothingDefined:
c.LogInvalidField("Input", defaultInput)
c.Input = defaultInput
default:
return errors.New("bad input type defined in config")
}
switch c.InputCodec {
case codecutil.H264, codecutil.MJPEG, codecutil.PCM, codecutil.ADPCM:
default:
switch c.Input {
case OutputAudio:
c.LogInvalidField("InputCodec", defaultAudioInputCodec)
c.InputCodec = defaultAudioInputCodec
default:
c.LogInvalidField("InputCodec", defaultInputCodec)
c.InputCodec = defaultInputCodec
} }
} }
if c.Outputs == nil {
c.LogInvalidField("Outputs", defaultOutput)
c.Outputs = append(c.Outputs, defaultOutput)
}
for i, o := range c.Outputs {
switch o {
case OutputFile, OutputHTTP, OutputRTP:
case OutputRTMP:
if c.RTMPURL == "" {
c.Logger.Log(logger.Info, "no RTMP URL: falling back to HTTP")
c.Outputs[i] = OutputHTTP
}
default:
return errors.New("bad output type defined in config")
}
}
if c.BurstPeriod == 0 {
c.LogInvalidField("BurstPeriod", defaultBurstPeriod)
c.BurstPeriod = defaultBurstPeriod
}
const maxMinFrames = 1000
switch {
case c.MinFrames == 0:
c.LogInvalidField("MinFrames", defaultMinFrames)
c.MinFrames = defaultMinFrames
case c.MinFrames < 0:
return errors.New("refresh period is less than 0")
case c.MinFrames > maxMinFrames:
return errors.New("refresh period is greater than 1000")
}
if c.FrameRate <= 0 {
c.LogInvalidField("FrameRate", defaultFrameRate)
c.FrameRate = defaultFrameRate
}
if c.WriteRate <= 0 {
c.LogInvalidField("writeRate", defaultWriteRate)
c.WriteRate = defaultWriteRate
}
if c.ClipDuration == 0 {
c.LogInvalidField("ClipDuration", defaultClipDuration)
c.ClipDuration = defaultClipDuration
} else if c.ClipDuration < 0 {
return errors.New("clip duration is less than 0")
}
if c.RTPAddress == "" {
c.LogInvalidField("RTPAddress", defaultRtpAddr)
c.RTPAddress = defaultRtpAddr
}
if c.RBWriteTimeout <= 0 {
c.Logger.Log(logger.Info, "RBWriteTimeout bad or unset, defaulting", "RBWriteTimeout", defaultRBWriteTimeout)
c.RBWriteTimeout = defaultRBWriteTimeout
}
if c.RBCapacity <= 0 {
c.Logger.Log(logger.Info, "RBCapacity bad or unset, defaulting", "RBCapacity", defaultRBCapacity)
c.RBCapacity = defaultRBCapacity
}
if c.PSITime <= 0 {
c.LogInvalidField("PSITime", defaultPSITime)
c.PSITime = defaultPSITime
}
if c.MinFPS <= 0 {
c.LogInvalidField("MinFPS", defaultMinFPS)
c.MinFPS = defaultMinFPS
}
if c.FileFPS <= 0 || (c.FileFPS > 0 && c.Input != InputFile) {
c.LogInvalidField("FileFPS", defaultFileFPS)
c.FileFPS = defaultFileFPS
}
return nil return nil
} }
// Update takes a map of configuration variable names and their corresponding
// values, parses the string values and converting into correct type, and then
// sets the config struct fields as appropriate.
func (c *Config) Update(vars map[string]string) {
for _, value := range Variables {
if v, ok := vars[value.Name]; ok && value.Update != nil {
value.Update(c, v)
}
}
}
func (c *Config) LogInvalidField(name string, def interface{}) { func (c *Config) LogInvalidField(name string, def interface{}) {
c.Logger.Log(logger.Info, name+" bad or unset, defaulting", name, def) c.Logger.Log(logger.Info, name+" bad or unset, defaulting", name, def)
} }
// stringInSlice returns true if want is in slice.
func stringInSlice(want string, slice []string) bool { func stringInSlice(want string, slice []string) bool {
for _, s := range slice { for _, s := range slice {
if s == want { if s == want {

177
revid/config/config_test.go Normal file
View File

@ -0,0 +1,177 @@
/*
DESCRIPTION
config_test.go provides testing for the Config struct methods (Validate and Update).
AUTHORS
Saxon A. Nelson-Milton <saxon@ausocean.org>
LICENSE
Copyright (C) 2020 the Australian Ocean Lab (AusOcean)
It is free software: you can redistribute it and/or modify them
under the terms of the GNU General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
It is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License
along with revid in gpl.txt. If not, see http://www.gnu.org/licenses.
*/
package config
import (
"testing"
"time"
"bitbucket.org/ausocean/av/codec/codecutil"
"bitbucket.org/ausocean/utils/logger"
"github.com/google/go-cmp/cmp"
)
type dumbLogger struct{}
func (dl *dumbLogger) Log(l int8, m string, a ...interface{}) {}
func (dl *dumbLogger) SetLevel(l int8) {}
func TestValidate(t *testing.T) {
dl := &dumbLogger{}
want := Config{
Logger: dl,
Input: defaultInput,
Outputs: []uint8{defaultOutput},
InputCodec: defaultInputCodec,
RTPAddress: defaultRTPAddr,
CameraIP: defaultCameraIP,
BurstPeriod: defaultBurstPeriod,
MinFrames: defaultMinFrames,
FrameRate: defaultFrameRate,
WriteRate: defaultWriteRate,
ClipDuration: defaultClipDuration,
PSITime: defaultPSITime,
FileFPS: defaultFileFPS,
RBCapacity: defaultRBCapacity,
RBWriteTimeout: defaultRBWriteTimeout,
MinFPS: defaultMinFPS,
}
got := Config{Logger: dl}
err := (&got).Validate()
if err != nil {
t.Fatalf("did not expect error: %v", err)
}
if !cmp.Equal(got, want) {
t.Errorf("configs not equal\nwant: %v\ngot: %v", want, got)
}
}
func TestUpdate(t *testing.T) {
updateMap := map[string]string{
"AutoWhiteBalance": "sun",
"BitDepth": "3",
"Bitrate": "200000",
"Brightness": "30",
"BurstPeriod": "10",
"CameraChan": "2",
"CameraIP": "192.168.1.5",
"CBR": "true",
"ClipDuration": "5",
"Exposure": "night",
"FileFPS": "30",
"Filters": "MOG",
"FrameRate": "30",
"Height": "300",
"HorizontalFlip": "true",
"HTTPAddress": "http://address",
"Input": "rtsp",
"InputCodec": "MJPEG",
"InputPath": "/inputpath",
"logging": "Error",
"Loop": "true",
"MinFPS": "30",
"MinFrames": "30",
"MotionDownscaling": "3",
"MotionHistory": "4",
"MotionInterval": "6",
"MotionKernel": "2",
"MotionMinArea": "9",
"MotionPadding": "8",
"MotionPixels": "100",
"MotionThreshold": "34",
"OutputPath": "/outputpath",
"Outputs": "Rtmp,Rtp",
"Quantization": "30",
"RBCapacity": "100000",
"RBWriteTimeout": "50",
"Rotation": "180",
"RTMPURL": "rtmp://url",
"RTPAddress": "ip:port",
"Saturation": "-10",
"VBRBitrate": "300000",
"VBRQuality": "excellent",
"VerticalFlip": "true",
"Width": "300",
}
dl := &dumbLogger{}
want := Config{
Logger: dl,
AutoWhiteBalance: "sun",
BitDepth: 3,
Bitrate: 200000,
Brightness: 30,
BurstPeriod: 10,
CameraChan: 2,
CameraIP: "192.168.1.5",
CBR: true,
ClipDuration: 5 * time.Second,
Exposure: "night",
FileFPS: 30,
Filters: []uint{FilterMOG},
FrameRate: 30,
Height: 300,
HorizontalFlip: true,
HTTPAddress: "http://address",
Input: InputRTSP,
InputCodec: codecutil.MJPEG,
InputPath: "/inputpath",
LogLevel: logger.Error,
Loop: true,
MinFPS: 30,
MinFrames: 30,
MotionDownscaling: 3,
MotionHistory: 4,
MotionInterval: 6,
MotionKernel: 2,
MotionMinArea: 9,
MotionPadding: 8,
MotionPixels: 100,
MotionThreshold: 34,
OutputPath: "/outputpath",
Outputs: []uint8{OutputRTMP, OutputRTP},
Quantization: 30,
RBCapacity: 100000,
RBWriteTimeout: 50,
Rotation: 180,
RTMPURL: "rtmp://url",
RTPAddress: "ip:port",
Saturation: -10,
VBRBitrate: 300000,
VBRQuality: QualityExcellent,
VerticalFlip: true,
Width: 300,
}
got := Config{Logger: dl}
got.Update(updateMap)
if !cmp.Equal(want, got) {
t.Errorf("configs not equal\nwant: %v\ngot: %v", want, got)
}
}

561
revid/config/variables.go Normal file
View File

@ -0,0 +1,561 @@
/*
DESCRIPTION
variables.go contains a list of structs that provide a variable Name, type in
a string format, a function for updating the variable in the Config struct
from a string, and finally, a validation function to check the validity of the
corresponding field value in the Config.
AUTHORS
Saxon A. Nelson-Milton <saxon@ausocean.org>
LICENSE
Copyright (C) 2020 the Australian Ocean Lab (AusOcean)
It is free software: you can redistribute it and/or modify them
under the terms of the GNU General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
It is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License
along with revid in gpl.txt. If not, see http://www.gnu.org/licenses.
*/
package config
import (
"fmt"
"strconv"
"strings"
"time"
"bitbucket.org/ausocean/av/codec/codecutil"
"bitbucket.org/ausocean/utils/logger"
)
// Default variable values.
const (
// General revid defaults.
defaultInput = InputRaspivid
defaultOutput = OutputHTTP
defaultInputCodec = codecutil.H264
defaultVerbosity = logger.Error
defaultRTPAddr = "localhost:6970"
defaultCameraIP = "192.168.1.50"
defaultBurstPeriod = 10 // Seconds
defaultMinFrames = 100
defaultFrameRate = 25
defaultWriteRate = 25
defaultClipDuration = 0
defaultAudioInputCodec = codecutil.ADPCM
defaultPSITime = 2
defaultFileFPS = 0
// Ring buffer defaults.
defaultRBCapacity = 50000000 // => 50MB
defaultRBWriteTimeout = 5 // Seconds.
// Motion filter parameter defaults.
defaultMinFPS = 1.0
)
var Variables = []struct {
Name string
Type_ string
Update func(*Config, string)
Validate func(*Config)
}{
{
Name: "AutoWhiteBalance",
Type_: "enum:off,auto,sun,cloud,shade,tungsten,fluorescent,incandescent,flash,horizon",
Update: func(c *Config, v string) { c.AutoWhiteBalance = v },
},
{
Name: "BitDepth",
Type_: "uint",
Update: func(c *Config, v string) { c.BitDepth = parseUint("BitDepth", v, c) },
},
{
Name: "Bitrate",
Type_: "uint",
Update: func(c *Config, v string) { c.Bitrate = parseUint("Bitrate", v, c) },
},
{
Name: "Brightness",
Type_: "uint",
Update: func(c *Config, v string) { c.Brightness = parseUint("Brightness", v, c) },
},
{
Name: "BurstPeriod",
Type_: "uint",
Update: func(c *Config, v string) { c.BurstPeriod = parseUint("BurstPeriod", v, c) },
Validate: func(c *Config) {
if c.BurstPeriod <= 0 {
c.LogInvalidField("BurstPeriod", defaultBurstPeriod)
c.BurstPeriod = defaultBurstPeriod
}
},
},
{
Name: "CameraChan",
Type_: "uint",
Update: func(c *Config, v string) { c.CameraChan = uint8(parseUint("CameraChan", v, c)) },
},
{
Name: "CameraIP",
Type_: "string",
Update: func(c *Config, v string) { c.CameraIP = v },
Validate: func(c *Config) {
if c.CameraIP == "" {
c.LogInvalidField("CameraIP", defaultCameraIP)
c.CameraIP = defaultCameraIP
}
},
},
{
Name: "CBR",
Type_: "bool",
Update: func(c *Config, v string) { c.CBR = parseBool("CBR", v, c) },
},
{
Name: "ClipDuration",
Type_: "uint",
Update: func(c *Config, v string) {
_v, err := strconv.Atoi(v)
if err != nil {
c.Logger.Log(logger.Warning, "invalid ClipDuration param", "value", v)
}
c.ClipDuration = time.Duration(_v) * time.Second
},
Validate: func(c *Config) {
if c.ClipDuration <= 0 {
c.LogInvalidField("ClipDuration", defaultClipDuration)
c.ClipDuration = defaultClipDuration
}
},
},
{
Name: "Exposure",
Type_: "enum:auto,night,nightpreview,backlight,spotlight,sports,snow,beach,verylong,fixedfps,antishake,fireworks",
Update: func(c *Config, v string) { c.Exposure = v },
},
{
Name: "FileFPS",
Type_: "uint",
Update: func(c *Config, v string) { c.FileFPS = parseUint("FileFPS", v, c) },
Validate: func(c *Config) {
if c.FileFPS <= 0 || (c.FileFPS > 0 && c.Input != InputFile) {
c.LogInvalidField("FileFPS", defaultFileFPS)
c.FileFPS = defaultFileFPS
}
},
},
{
Name: "Filters",
Type_: "enums:NoOp,MOG,VariableFPS,KNN,Difference,Basic",
Update: func(c *Config, v string) {
filters := strings.Split(v, ",")
m := map[string]uint{"NoOp": FilterNoOp, "MOG": FilterMOG, "VariableFPS": FilterVariableFPS, "KNN": FilterKNN, "Difference": FilterDiff, "Basic": FilterBasic}
c.Filters = make([]uint, len(filters))
for i, filter := range filters {
v, ok := m[filter]
if !ok {
c.Logger.Log(logger.Warning, "invalid Filters param", "value", v)
}
c.Filters[i] = uint(v)
}
},
},
{
Name: "FrameRate",
Type_: "uint",
Update: func(c *Config, v string) { c.FrameRate = parseUint("FrameRate", v, c) },
Validate: func(c *Config) {
if c.FrameRate <= 0 || c.FrameRate > 60 {
c.LogInvalidField("FrameRate", defaultFrameRate)
c.FrameRate = defaultFrameRate
}
},
},
{
Name: "Height",
Type_: "uint",
Update: func(c *Config, v string) { c.Height = parseUint("Height", v, c) },
},
{
Name: "HorizontalFlip",
Type_: "bool",
Update: func(c *Config, v string) { c.HorizontalFlip = parseBool("HorizontalFlip", v, c) },
},
{
Name: "HTTPAddress",
Type_: "string",
Update: func(c *Config, v string) { c.HTTPAddress = v },
},
{
Name: "Input",
Type_: "enum:raspivid,rtsp,v4l,file,audio",
Update: func(c *Config, v string) {
c.Input = parseEnum(
"Input",
v,
map[string]uint8{
"raspivid": InputRaspivid,
"rtsp": InputRTSP,
"v4l": InputV4L,
"file": InputFile,
"audio": InputAudio,
},
c,
)
},
Validate: func(c *Config) {
switch c.Input {
case InputRaspivid, InputV4L, InputFile, InputAudio, InputRTSP:
default:
c.LogInvalidField("Input", defaultInput)
c.Input = defaultInput
}
},
},
{
Name: "InputCodec",
Type_: "enum:H264,H265,MJPEG,PCM,ADPCM",
Update: func(c *Config, v string) {
c.InputCodec = parseEnum(
"InputCodec",
v,
map[string]uint8{
"h264": codecutil.H264,
"h265": codecutil.H265,
"mjpeg": codecutil.MJPEG,
"pcm": codecutil.PCM,
"adpcm": codecutil.ADPCM,
},
c,
)
},
Validate: func(c *Config) {
switch c.InputCodec {
case codecutil.H264, codecutil.MJPEG, codecutil.PCM, codecutil.ADPCM:
default:
switch c.Input {
case OutputAudio:
c.LogInvalidField("InputCodec", defaultAudioInputCodec)
c.InputCodec = defaultAudioInputCodec
default:
c.LogInvalidField("InputCodec", defaultInputCodec)
c.InputCodec = defaultInputCodec
}
}
},
},
{
Name: "InputPath",
Type_: "string",
Update: func(c *Config, v string) { c.InputPath = v },
},
{
Name: "logging",
Type_: "enum:Debug,Info,Warning,Error,Fatal",
Update: func(c *Config, v string) {
switch v {
case "Debug":
c.LogLevel = logger.Debug
case "Info":
c.LogLevel = logger.Info
case "Warning":
c.LogLevel = logger.Warning
case "Error":
c.LogLevel = logger.Error
case "Fatal":
c.LogLevel = logger.Fatal
default:
c.Logger.Log(logger.Warning, "invalid Logging param", "value", v)
}
},
Validate: func(c *Config) {
switch c.LogLevel {
case logger.Debug, logger.Info, logger.Warning, logger.Error, logger.Fatal:
default:
c.LogInvalidField("LogLevel", defaultVerbosity)
c.LogLevel = defaultVerbosity
}
},
},
{
Name: "Loop",
Type_: "bool",
Update: func(c *Config, v string) { c.Loop = parseBool("Loop", v, c) },
},
{
Name: "MinFPS",
Type_: "uint",
Update: func(c *Config, v string) { c.MinFPS = parseUint("MinFPS", v, c) },
Validate: func(c *Config) { c.MinFPS = lessThanOrEqual("MinFPS", c.MinFPS, 0, c, defaultMinFPS) },
},
{
Name: "MinFrames",
Type_: "uint",
Update: func(c *Config, v string) { c.MinFrames = parseUint("MinFrames", v, c) },
Validate: func(c *Config) {
const maxMinFrames = 1000
if c.MinFrames <= 0 || c.MinFrames > maxMinFrames {
c.LogInvalidField("MinFrames", defaultMinFrames)
c.MinFrames = defaultMinFrames
}
},
},
{
Name: "mode",
Type_: "enum:Normal,Paused,Burst,Loop",
Update: func(c *Config, v string) {
c.Loop = false
if v == "Loop" {
c.Loop = true
}
},
},
{
Name: "MotionDownscaling",
Type_: "uint",
Update: func(c *Config, v string) { c.MotionDownscaling = parseUint("MotionDownscaling", v, c) },
},
{
Name: "MotionHistory",
Type_: "uint",
Update: func(c *Config, v string) { c.MotionHistory = parseUint("MotionHistory", v, c) },
},
{
Name: "MotionInterval",
Type_: "uint",
Update: func(c *Config, v string) { c.MotionInterval = parseUint("MotionInterval", v, c) },
},
{
Name: "MotionKernel",
Type_: "uint",
Update: func(c *Config, v string) { c.MotionKernel = parseUint("MotionKernel", v, c) },
},
{
Name: "MotionMinArea",
Type_: "float",
Update: func(c *Config, v string) {
f, err := strconv.ParseFloat(v, 64)
if err != nil {
c.Logger.Log(logger.Warning, "invalid MotionMinArea var", "value", v)
}
c.MotionMinArea = f
},
},
{
Name: "MotionPadding",
Type_: "uint",
Update: func(c *Config, v string) { c.MotionPadding = parseUint("MotionPadding", v, c) },
},
{
Name: "MotionPixels",
Type_: "uint",
Update: func(c *Config, v string) { c.MotionPixels = parseUint("MotionPixels", v, c) },
},
{
Name: "MotionThreshold",
Type_: "float",
Update: func(c *Config, v string) {
f, err := strconv.ParseFloat(v, 64)
if err != nil {
c.Logger.Log(logger.Warning, "invalid MotionThreshold var", "value", v)
}
c.MotionThreshold = f
},
},
{
Name: "Output",
Type_: "enum:File,HTTP,RTMP,RTP",
Update: func(c *Config, v string) {
c.Outputs = make([]uint8, 1)
switch strings.ToLower(v) {
case "file":
c.Outputs[0] = OutputFile
case "http":
c.Outputs[0] = OutputHTTP
case "rtmp":
c.Outputs[0] = OutputRTMP
case "rtp":
c.Outputs[0] = OutputRTP
default:
c.Logger.Log(logger.Warning, "invalid output param", "value", v)
}
},
},
{
Name: "OutputPath",
Type_: "string",
Update: func(c *Config, v string) { c.OutputPath = v },
},
{
Name: "Outputs",
Type_: "enums:File,HTTP,RTMP,RTP",
Update: func(c *Config, v string) {
outputs := strings.Split(v, ",")
c.Outputs = make([]uint8, len(outputs))
for i, output := range outputs {
switch strings.ToLower(output) {
case "file":
c.Outputs[i] = OutputFile
case "http":
c.Outputs[i] = OutputHTTP
case "rtmp":
c.Outputs[i] = OutputRTMP
case "rtp":
c.Outputs[i] = OutputRTP
default:
c.Logger.Log(logger.Warning, "invalid outputs param", "value", v)
}
}
},
Validate: func(c *Config) {
if c.Outputs == nil {
c.LogInvalidField("Outputs", defaultOutput)
c.Outputs = append(c.Outputs, defaultOutput)
}
},
},
{
Name: "PSITime",
Type_: "uint",
Update: func(c *Config, v string) { c.PSITime = parseUint("PSITime", v, c) },
Validate: func(c *Config) { c.PSITime = lessThanOrEqual("PSITime", c.PSITime, 0, c, defaultPSITime) },
},
{
Name: "Quantization",
Type_: "uint",
Update: func(c *Config, v string) { c.Quantization = parseUint("Quantization", v, c) },
},
{
Name: "RBCapacity",
Type_: "uint",
Update: func(c *Config, v string) { c.RBCapacity = parseUint("RBCapacity", v, c) },
Validate: func(c *Config) { c.RBCapacity = lessThanOrEqual("RBCapacity", c.RBCapacity, 0, c, defaultRBCapacity) },
},
{
Name: "RBWriteTimeout",
Type_: "uint",
Update: func(c *Config, v string) { c.RBWriteTimeout = parseUint("RBWriteTimeout", v, c) },
Validate: func(c *Config) {
c.RBWriteTimeout = lessThanOrEqual("RBWriteTimeout", c.RBWriteTimeout, 0, c, defaultRBWriteTimeout)
},
},
{
Name: "Rotation",
Type_: "uint",
Update: func(c *Config, v string) { c.Rotation = parseUint("Rotation", v, c) },
},
{
Name: "RTMPURL",
Type_: "string",
Update: func(c *Config, v string) { c.RTMPURL = v },
},
{
Name: "RTPAddress",
Type_: "string",
Update: func(c *Config, v string) { c.RTPAddress = v },
Validate: func(c *Config) {
if c.RTPAddress == "" {
c.LogInvalidField("RTPAddress", defaultRTPAddr)
c.RTPAddress = defaultRTPAddr
}
},
},
{
Name: "Saturation",
Type_: "int",
Update: func(c *Config, v string) {
_v, err := strconv.Atoi(v)
if err != nil {
c.Logger.Log(logger.Warning, "invalid saturation param", "value", v)
}
c.Saturation = _v
},
},
{
Name: "VBRBitrate",
Type_: "uint",
Update: func(c *Config, v string) { c.VBRBitrate = parseUint("VBRBitrate", v, c) },
},
{
Name: "VBRQuality",
Type_: "enum:standard,fair,good,great,excellent",
Update: func(c *Config, v string) {
c.VBRQuality = Quality(parseEnum(
"VBRQuality",
v,
map[string]uint8{
"standard": uint8(QualityStandard),
"fair": uint8(QualityFair),
"good": uint8(QualityGood),
"great": uint8(QualityGreat),
"excellent": uint8(QualityExcellent),
},
c,
))
},
},
{
Name: "VerticalFlip",
Type_: "bool",
Update: func(c *Config, v string) { c.VerticalFlip = parseBool("VerticalFlip", v, c) },
},
{
Name: "Width",
Type_: "uint",
Update: func(c *Config, v string) { c.Width = parseUint("Width", v, c) },
},
{
Name: "WriteRate",
Type_: "uint",
Update: func(c *Config, v string) { c.WriteRate = float64(parseUint("WriteRate", v, c)) },
Validate: func(c *Config) {
c.WriteRate = float64(lessThanOrEqual("WriteRate", uint(c.WriteRate), 0, c, defaultWriteRate))
},
},
}
func parseUint(n, v string, c *Config) uint {
_v, err := strconv.Atoi(v)
if err != nil {
c.Logger.Log(logger.Warning, fmt.Sprintf("invalid %s param", n), "value", v)
}
return uint(_v)
}
func parseBool(n, v string, c *Config) (b bool) {
switch strings.ToLower(v) {
case "true":
b = true
case "false":
b = false
default:
c.Logger.Log(logger.Warning, fmt.Sprintf("invalid value for %s param", n), "value", v)
}
return
}
func parseEnum(n, v string, enums map[string]uint8, c *Config) uint8 {
_v, ok := enums[strings.ToLower(v)]
if !ok {
c.Logger.Log(logger.Warning, fmt.Sprintf("invalid value for %s param", n), "value", v)
}
return _v
}
func lessThanOrEqual(n string, v, cmp uint, c *Config, def uint) uint {
if v <= cmp {
c.LogInvalidField(n, def)
return def
}
return v
}

View File

@ -33,8 +33,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"strconv"
"strings"
"sync" "sync"
"time" "time"
@ -557,324 +555,7 @@ func (r *Revid) Update(vars map[string]string) error {
defer r.mu.Unlock() defer r.mu.Unlock()
//look through the vars and update revid where needed //look through the vars and update revid where needed
r.cfg.Logger.Log(logger.Debug, "checking vars from server", "vars", vars) r.cfg.Logger.Log(logger.Debug, "checking vars from server", "vars", vars)
for key, value := range vars { r.cfg.Update(vars)
switch key {
case "Input":
v, ok := map[string]uint8{"raspivid": config.InputRaspivid, "rtsp": config.InputRTSP, "v4l": config.InputV4L, "file": config.InputFile}[strings.ToLower(value)]
if !ok {
r.cfg.Logger.Log(logger.Warning, "invalid input var", "value", value)
break
}
r.cfg.Input = v
case "Saturation":
s, err := strconv.Atoi(value)
if err != nil {
r.cfg.Logger.Log(logger.Warning, "invalid saturation param", "value", value)
break
}
r.cfg.Saturation = int(s)
case "Brightness":
b, err := strconv.Atoi(value)
if err != nil {
r.cfg.Logger.Log(logger.Warning, "invalid brightness param", "value", value)
break
}
r.cfg.Brightness = uint(b)
case "Exposure":
r.cfg.Exposure = value
case "AutoWhiteBalance":
r.cfg.AutoWhiteBalance = value
case "InputCodec":
switch value {
case "H264":
r.cfg.InputCodec = codecutil.H264
case "MJPEG":
r.cfg.InputCodec = codecutil.MJPEG
default:
r.cfg.Logger.Log(logger.Warning, "invalid InputCodec variable value", "value", value)
}
case "Outputs":
outputs := strings.Split(value, ",")
r.cfg.Outputs = make([]uint8, len(outputs))
for i, output := range outputs {
switch output {
case "File":
r.cfg.Outputs[i] = config.OutputFile
case "Http":
r.cfg.Outputs[i] = config.OutputHTTP
case "Rtmp":
r.cfg.Outputs[i] = config.OutputRTMP
case "Rtp":
r.cfg.Outputs[i] = config.OutputRTP
default:
r.cfg.Logger.Log(logger.Warning, "invalid outputs param", "value", value)
continue
}
}
case "Output":
r.cfg.Outputs = make([]uint8, 1)
switch strings.ToLower(value) {
case "file":
r.cfg.Outputs[0] = config.OutputFile
case "http":
r.cfg.Outputs[0] = config.OutputHTTP
case "rtmp":
r.cfg.Outputs[0] = config.OutputRTMP
case "rtp":
r.cfg.Outputs[0] = config.OutputRTP
default:
r.cfg.Logger.Log(logger.Warning, "invalid output param", "value", value)
continue
}
case "RTMPURL":
r.cfg.RTMPURL = value
case "RTPAddress":
r.cfg.RTPAddress = value
case "Bitrate":
v, err := strconv.Atoi(value)
if err != nil {
r.cfg.Logger.Log(logger.Warning, "invalid framerate param", "value", value)
break
}
r.cfg.Bitrate = uint(v)
case "OutputPath":
r.cfg.OutputPath = value
case "InputPath":
r.cfg.InputPath = value
case "Height":
h, err := strconv.Atoi(value)
if err != nil {
r.cfg.Logger.Log(logger.Warning, "invalid height param", "value", value)
break
}
r.cfg.Height = uint(h)
case "Width":
w, err := strconv.Atoi(value)
if err != nil {
r.cfg.Logger.Log(logger.Warning, "invalid width param", "value", value)
break
}
r.cfg.Width = uint(w)
case "FrameRate":
v, err := strconv.Atoi(value)
if err != nil {
r.cfg.Logger.Log(logger.Warning, "invalid framerate param", "value", value)
break
}
r.cfg.FrameRate = uint(v)
case "Rotation":
v, err := strconv.Atoi(value)
if err != nil || v > 359 {
r.cfg.Logger.Log(logger.Warning, "invalid rotation param", "value", value)
break
}
r.cfg.Rotation = uint(v)
case "HTTPAddress":
r.cfg.HTTPAddress = value
case "Quantization":
v, err := strconv.Atoi(value)
if err != nil {
r.cfg.Logger.Log(logger.Warning, "invalid quantization param", "value", v)
break
}
r.cfg.Quantization = uint(v)
case "MinFrames":
v, err := strconv.Atoi(value)
if err != nil {
r.cfg.Logger.Log(logger.Warning, "invalid MinFrames param", "value", value)
break
}
r.cfg.MinFrames = uint(v)
case "ClipDuration":
v, err := strconv.Atoi(value)
if err != nil {
r.cfg.Logger.Log(logger.Warning, "invalid ClipDuration param", "value", value)
break
}
r.cfg.ClipDuration = time.Duration(v) * time.Second
case "HorizontalFlip":
switch strings.ToLower(value) {
case "true":
r.cfg.HorizontalFlip = true
case "false":
r.cfg.HorizontalFlip = false
default:
r.cfg.Logger.Log(logger.Warning, "invalid HorizontalFlip param", "value", value)
}
case "VerticalFlip":
switch strings.ToLower(value) {
case "true":
r.cfg.VerticalFlip = true
case "false":
r.cfg.VerticalFlip = false
default:
r.cfg.Logger.Log(logger.Warning, "invalid VerticalFlip param", "value", value)
}
case "Filters":
filters := strings.Split(value, ",")
m := map[string]int{"NoOp": config.FilterNoOp, "MOG": config.FilterMOG, "VariableFPS": config.FilterVariableFPS, "KNN": config.FilterKNN, "Difference": config.FilterDiff, "Basic": config.FilterBasic}
r.cfg.Filters = make([]uint8, len(filters))
for i, filter := range filters {
v, ok := m[filter]
if !ok {
r.cfg.Logger.Log(logger.Warning, "invalid Filters param", "value", value)
}
r.cfg.Filters[i] = uint8(v)
}
case "PSITime":
v, err := strconv.Atoi(value)
if err != nil || v < 0 {
r.cfg.Logger.Log(logger.Warning, "invalid PSITime var", "value", value)
break
}
r.cfg.PSITime = uint(v)
case "BurstPeriod":
v, err := strconv.Atoi(value)
if err != nil {
r.cfg.Logger.Log(logger.Warning, "invalid BurstPeriod param", "value", value)
break
}
r.cfg.BurstPeriod = uint(v)
case "logging":
switch value {
case "Debug":
r.cfg.LogLevel = logger.Debug
case "Info":
r.cfg.LogLevel = logger.Info
case "Warning":
r.cfg.LogLevel = logger.Warning
case "Error":
r.cfg.LogLevel = logger.Error
case "Fatal":
r.cfg.LogLevel = logger.Fatal
default:
r.cfg.Logger.Log(logger.Warning, "invalid Logging param", "value", value)
}
case "RBCapacity":
v, err := strconv.Atoi(value)
if err != nil || v < 0 {
r.cfg.Logger.Log(logger.Warning, "invalid RBCapacity var", "value", value)
break
}
r.cfg.RBCapacity = uint(v)
case "RBWriteTimeout":
v, err := strconv.Atoi(value)
if err != nil || v <= 0 {
r.cfg.Logger.Log(logger.Warning, "invalid RBWriteTimeout var", "value", value)
break
}
r.cfg.RBWriteTimeout = uint(v)
case "CBR":
v, ok := map[string]bool{"true": true, "false": false}[strings.ToLower(value)]
if !ok {
r.cfg.Logger.Log(logger.Warning, "invalid CBR var", "value", value)
break
}
r.cfg.CBR = v
case "CameraIP":
r.cfg.CameraIP = value
case "VBRQuality":
v, ok := map[string]config.Quality{"standard": config.QualityStandard, "fair": config.QualityFair, "good": config.QualityGood, "great": config.QualityGreat, "excellent": config.QualityExcellent}[strings.ToLower(value)]
if !ok {
r.cfg.Logger.Log(logger.Warning, "invalid VBRQuality var", "value", value)
break
}
r.cfg.VBRQuality = v
case "VBRBitrate":
v, err := strconv.Atoi(value)
if err != nil || v <= 0 {
r.cfg.Logger.Log(logger.Warning, "invalid VBRBitrate var", "value", value)
break
}
r.cfg.VBRBitrate = uint(v)
case "CameraChan":
v, err := strconv.Atoi(value)
if err != nil || (v != 1 && v != 2) {
r.cfg.Logger.Log(logger.Warning, "invalid CameraChan var", "value", value)
break
}
r.cfg.CameraChan = uint8(v)
case "MinFPS":
v, err := strconv.ParseFloat(value, 64)
if err != nil {
r.cfg.Logger.Log(logger.Warning, "invalid MinFPS var", "value", value)
break
}
r.cfg.MinFPS = v
case "MotionMinArea":
v, err := strconv.ParseFloat(value, 64)
if err != nil {
r.cfg.Logger.Log(logger.Warning, "invalid MotionMinArea var", "value", value)
break
}
r.cfg.MotionMinArea = v
case "MotionThreshold":
v, err := strconv.ParseFloat(value, 64)
if err != nil {
r.cfg.Logger.Log(logger.Warning, "invalid MotionThreshold var", "value", value)
break
}
r.cfg.MotionThreshold = v
case "MotionKernel":
v, err := strconv.Atoi(value)
if err != nil {
r.cfg.Logger.Log(logger.Warning, "invalid MotionKernel var", "value", value)
break
}
r.cfg.MotionKernel = uint(v)
case "MotionHistory":
v, err := strconv.Atoi(value)
if err != nil || v <= 0 {
r.cfg.Logger.Log(logger.Warning, "invalid MotionHistory var", "value", value)
break
}
r.cfg.MotionHistory = uint(v)
case "MotionPadding":
v, err := strconv.Atoi(value)
if err != nil || v <= 0 {
r.cfg.Logger.Log(logger.Warning, "invalid MotionPadding var", "value", value)
break
}
r.cfg.MotionPadding = uint(v)
case "MotionPixels":
v, err := strconv.Atoi(value)
if err != nil {
r.cfg.Logger.Log(logger.Warning, "invalid MotionPixels var", "value", value)
break
}
r.cfg.MotionPixels = uint(v)
case "MotionDownscaling":
v, err := strconv.Atoi(value)
if err != nil {
r.cfg.Logger.Log(logger.Warning, "invalid MotionDownscaling var", "value", value)
break
}
r.cfg.MotionDownscaling = uint(v)
case "MotionInterval":
v, err := strconv.Atoi(value)
if err != nil || v < 0 {
r.cfg.Logger.Log(logger.Warning, "invalid MotionInterval var", "value", value)
break
}
r.cfg.MotionInterval = uint(v)
case "FileFPS":
v, err := strconv.Atoi(value)
if err != nil {
r.cfg.Logger.Log(logger.Warning, "invalid FileFPS var", "value", value)
break
}
r.cfg.FileFPS = uint(v)
case "mode":
r.cfg.Loop = false
if value == "Loop" {
r.cfg.Loop = true
}
}
}
r.cfg.Logger.Log(logger.Info, "finished reconfig") r.cfg.Logger.Log(logger.Info, "finished reconfig")
r.cfg.Logger.Log(logger.Debug, "config changed", "config", r.cfg) r.cfg.Logger.Log(logger.Debug, "config changed", "config", r.cfg)
return nil return nil