mirror of https://bitbucket.org/ausocean/av.git
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:
parent
a286d6c140
commit
ce50b5fbd1
|
@ -136,7 +136,7 @@ func main() {
|
|||
var rv *revid.Revid
|
||||
|
||||
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 {
|
||||
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
|
||||
// CPU profiler.
|
||||
func profile(l *logger.Logger) {
|
||||
|
|
|
@ -41,7 +41,7 @@ type VariableFPS 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)
|
||||
return &VariableFPS{filter, dst, frames, 0}
|
||||
}
|
||||
|
|
1
go.mod
1
go.mod
|
@ -8,6 +8,7 @@ require (
|
|||
github.com/Comcast/gots v0.0.0-20190305015453-8d56e473f0f7
|
||||
github.com/go-audio/audio v0.0.0-20181013203223-7b2a6ca21480
|
||||
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/pkg/errors v0.8.1
|
||||
github.com/yobert/alsa v0.0.0-20180630182551-d38d89fa843e
|
||||
|
|
3
go.sum
3
go.sum
|
@ -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/go.mod h1:UiqzUyfX0zs3pJ/DPyvS5v8sN6s5bXPUDDIVA5v8dks=
|
||||
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/kidoman/embd v0.0.0-20170508013040-d3d8c0c5c68d/go.mod h1:ACKj9jnzOzj1lw2ETilpFGK7L9dtJhAzT7T1OhAGtRQ=
|
||||
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/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/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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
|
|
|
@ -27,10 +27,8 @@ LICENSE
|
|||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"bitbucket.org/ausocean/av/codec/codecutil"
|
||||
"bitbucket.org/ausocean/utils/logger"
|
||||
)
|
||||
|
||||
|
@ -65,33 +63,6 @@ const (
|
|||
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.
|
||||
type Quality int
|
||||
|
||||
|
@ -259,7 +230,7 @@ type Config struct {
|
|||
|
||||
HorizontalFlip bool // HorizontalFlip flips video horizontally 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.
|
||||
|
||||
// Ring buffer parameters.
|
||||
|
@ -268,7 +239,7 @@ type Config struct {
|
|||
|
||||
// Motion filter parameters.
|
||||
// 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).
|
||||
MotionDownscaling uint // Downscaling factor of frames used for motion detection.
|
||||
|
||||
|
@ -286,187 +257,32 @@ type Config struct {
|
|||
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
|
||||
// if particular parameters have not been defined.
|
||||
func (c *Config) Validate() error {
|
||||
switch c.LogLevel {
|
||||
case logger.Debug, logger.Info, logger.Warning, logger.Error, logger.Fatal:
|
||||
default:
|
||||
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
|
||||
for _, v := range Variables {
|
||||
if v.Validate != nil {
|
||||
v.Validate(c)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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{}) {
|
||||
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 {
|
||||
for _, s := range slice {
|
||||
if s == want {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
321
revid/revid.go
321
revid/revid.go
|
@ -33,8 +33,6 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
|
@ -557,324 +555,7 @@ func (r *Revid) Update(vars map[string]string) error {
|
|||
defer r.mu.Unlock()
|
||||
//look through the vars and update revid where needed
|
||||
r.cfg.Logger.Log(logger.Debug, "checking vars from server", "vars", vars)
|
||||
for key, value := range 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.Update(vars)
|
||||
r.cfg.Logger.Log(logger.Info, "finished reconfig")
|
||||
r.cfg.Logger.Log(logger.Debug, "config changed", "config", r.cfg)
|
||||
return nil
|
||||
|
|
Loading…
Reference in New Issue