/* NAME RevidCLI.go DESCRIPTION See Readme.md AUTHORS Saxon A. Nelson-Milton Jack Richardson LICENSE RevidCLI.go is Copyright (C) 2017-2018 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 main import ( "flag" "fmt" "os" "strconv" "time" "bitbucket.org/ausocean/av/revid" "bitbucket.org/ausocean/iot/pi/netsender" "bitbucket.org/ausocean/utils/smartlogger" ) const ( // progName is the program name for logging purposes. progName = "revid-cli" // Logging is set to INFO level. loggerVerbosity = 3 ) // Indexes for configFlags const ( inputPtr = iota inputCodecPtr outputPtr rtmpMethodPtr packetizationPtr quantizationModePtr verbosityPtr framesPerClipPtr rtmpUrlPtr bitratePtr outputFileNamePtr inputFileNamePtr heightPtr widthPtr frameRatePtr httpAddressPtr quantizationPtr timeoutPtr intraRefreshPeriodPtr verticalFlipPtr horizontalFlipPtr noOfConfigFlags ) // Other misc consts const ( netSendRetryTime = 5 * time.Second defaultRunDuration = 24 * time.Hour revidStopTime = 5 * time.Second prepTime = 20 * time.Second ) // Globals var ( revidInst *revid.Revid config revid.Config ) func main() { flagNames := [noOfConfigFlags]struct{ name, description string }{ {"Input", "The input type"}, {"InputCodec", "The codec of the input"}, {"Output", "The output type"}, {"RtmpMethod", "The method used to send over rtmp (ffmpeg or librtmp)"}, {"Packetization", "The method of data packetisation"}, {"QuantizationMode", "The level of quantization"}, {"Verbosity", "Verbosity on or off"}, {"FramesPerClip", "Number of frames per clip sent"}, {"RtmpUrl", "Url of rtmp endpoint"}, {"Bitrate", "Bitrate of recorded video"}, {"OutputFileName", "The directory of the output file"}, {"InputFileName", "The directory of the input file"}, {"Height", "Height in pixels"}, {"Width", "Width in pixels"}, {"FrameRate", "Frame rate of captured video"}, {"HttpAddress", "Destination address of http posts"}, {"Quantization", "Desired quantization value"}, {"Timeout", "Http timeout in seconds"}, {"IntraRefreshPeriod", "The IntraRefreshPeriod i.e. how many keyframes we send"}, {"VerticalFlip", "Flip video vertically"}, {"HorizontalFlip", "Flip video horizontally"}, } // Create the configFlags based on the flagNames array configFlags := make([](*string), noOfConfigFlags) for i, f := range &flagNames { configFlags[i] = flag.String(f.name, "", f.description) } // Do we want a netsender session useNetsender := flag.Bool("NetSender", false, "Are we checking vars through netsender?") // User might also want to define how long revid runs for runDurationPtr := flag.Duration("runDuration", defaultRunDuration, "How long do you want revid to run for?") flag.Parse() switch *configFlags[inputPtr] { case "Raspivid": config.Input = revid.Raspivid case "Rtp": config.Input = revid.Rtp case "File": config.Input = revid.File case "": default: fmt.Println("Bad input argument!") } switch *configFlags[inputCodecPtr] { case "H264Codec": config.InputCodec = revid.H264Codec case "": default: fmt.Println("Bad input codec argument!") } switch *configFlags[outputPtr] { case "File": config.Output = revid.File case "Http": config.Output = revid.Http case "NativeRtmp": config.Output = revid.NativeRtmp case "FfmpegRtmp": config.Output = revid.FfmpegRtmp case "": default: fmt.Println("Bad output argument!") } switch *configFlags[rtmpMethodPtr] { case "Ffmpeg": config.RtmpMethod = revid.Ffmpeg case "LibRtmp": config.RtmpMethod = revid.LibRtmp case "": default: fmt.Println("Bad rtmp method argument!") } switch *configFlags[packetizationPtr] { case "None": config.Packetization = revid.None case "Rtp": config.Packetization = revid.Rtp case "Flv": config.Packetization = revid.Flv case "": default: fmt.Println("Bad packetization argument!") } switch *configFlags[quantizationModePtr] { case "QuantizationOn": config.QuantizationMode = revid.QuantizationOn case "QuantizationOff": config.QuantizationMode = revid.QuantizationOff case "": default: fmt.Println("Bad quantization mode argument!") } switch *configFlags[verbosityPtr] { case "No": config.Verbosity = revid.No case "Yes": config.Verbosity = revid.Yes case "": default: fmt.Println("Bad verbosity argument!") } switch *configFlags[horizontalFlipPtr] { case "No": config.HorizontalFlip = revid.No case "Yes": config.HorizontalFlip = revid.Yes case "": config.HorizontalFlip = revid.No default: fmt.Println("Bad horizontal flip option!") } switch *configFlags[verticalFlipPtr] { case "No": config.VerticalFlip = revid.No case "Yes": config.VerticalFlip = revid.Yes case "": config.VerticalFlip = revid.No default: fmt.Println("Bad vertical flip option!") } fpc, err := strconv.Atoi(*configFlags[framesPerClipPtr]) if err == nil && fpc > 0 { config.FramesPerClip = fpc } config.RtmpUrl = *configFlags[rtmpUrlPtr] config.Bitrate = *configFlags[bitratePtr] config.OutputFileName = *configFlags[outputFileNamePtr] config.InputFileName = *configFlags[inputFileNamePtr] config.Height = *configFlags[heightPtr] config.Width = *configFlags[widthPtr] config.FrameRate = *configFlags[frameRatePtr] config.HttpAddress = *configFlags[httpAddressPtr] config.Quantization = *configFlags[quantizationPtr] config.Timeout = *configFlags[timeoutPtr] config.IntraRefreshPeriod = *configFlags[intraRefreshPeriodPtr] var ns netsender.Sender var vs int paused := false if *useNetsender { // initialize NetSender and use NetSender's logger config.Logger = netsender.Logger() config.Logger.Log(progName, "Info", "Running in NetSender mode") err := ns.Init(nil, nil, nil) if err != nil { config.Logger.Log(progName, "Error", err.Error()) // TODO(kortschak): Make this "Fatal" when that exists. os.Exit(1) } vars, _ := ns.Vars() if vars["mode"] == "Paused" { paused = true } } else { // alternatively, instantiate our own logger config.Logger = smartlogger.New(loggerVerbosity, smartlogger.File, "/var/log/netsender/") } if !paused { startRevid() } // loop in NetSender mode for *useNetsender { if err := sendTo(&ns); err != nil { config.Logger.Log(progName, "Warning", err.Error()) time.Sleep(netSendRetryTime) continue } if vs != ns.VarSum() { // vars changed vars, err := ns.Vars() if err != nil { config.Logger.Log(progName, "Warning", err.Error()) time.Sleep(netSendRetryTime) continue } vs = ns.VarSum() if vars["mode"] == "Paused" { if !paused { config.Logger.Log(progName, "Info", "Pausing revid") stopRevid() paused = true } } else { updateRevid(vars, !paused) if paused { paused = false } } } sleepTime, _ := strconv.Atoi(ns.Param("mp")) time.Sleep(time.Duration(sleepTime) * time.Second) } // If we're not running a netsender session then we run revid for the amount // of time the user defined or the default 2 days time.Sleep(*runDurationPtr) stopRevid() } // sendTo implements our main NetSender client and handles NetReceiver configuration // (as distinct from httpSender which just sends video data). func sendTo(ns *netsender.Sender) error { // populate input values, if any inputs := netsender.MakePins(ns.Param("ip"), "X") for i, pin := range inputs { if pin.Name == "X23" { inputs[i].Value = revidInst.Bitrate() } } _, reconfig, err := ns.Send(netsender.RequestPoll, inputs) if err != nil { return err } if reconfig { return ns.Config() } return nil } // wrappers for stopping and starting revid func startRevid() { createRevidInstance() revidInst.Start() } func createRevidInstance() { // Try to create the revid instance with the given config var err error for revidInst, err = revid.New(config); err != nil; { // If the config does have a logger, use it to output error, otherwise // just output to std output if config.Logger != nil { config.Logger.Log(progName, "FATAL ERROR", err.Error()) } else { fmt.Printf("FATAL ERROR: %v", err.Error()) } } } func stopRevid() { revidInst.Stop() // FIXME(kortschak): Is this waiting on completion of work? // Use a wait group and Wait method if it is. time.Sleep(revidStopTime) } func updateRevid(vars map[string]string, stop bool) { if stop { stopRevid() } //look through the vars and update revid where needed for key, value := range vars { switch key { case "FramesPerClip": fpc, err := strconv.Atoi(value) if fpc > 0 && err == nil { config.FramesPerClip = fpc } case "RtmpUrl": config.RtmpUrl = value case "Bitrate": revidInst.Log(revid.Debug, fmt.Sprintf("The value: %v", value)) asInt, err := strconv.Atoi(value) revidInst.Log(revid.Debug, fmt.Sprintf("Bitrate as integer: %v", asInt)) if asInt > 0 && err == nil { revidInst.Log(revid.Debug, "Updating bitrate") config.Bitrate = value } case "OutputFileName": config.OutputFileName = value case "InputFileName": config.InputFileName = value case "Height": asInt, err := strconv.Atoi(value) if asInt > 0 && err == nil { config.Height = value } case "Width": asInt, err := strconv.Atoi(value) if asInt > 0 && err == nil { config.Width = value } case "FrameRate": asInt, err := strconv.Atoi(value) if asInt > 0 && err == nil { config.FrameRate = value } case "HttpAddress": config.HttpAddress = value case "Quantization": asInt, err := strconv.Atoi(value) if asInt > 0 && err == nil { config.Quantization = value } case "Timeout": asInt, err := strconv.Atoi(value) if asInt > 0 && err == nil { config.Timeout = value } case "IntraRefreshPeriod": asInt, err := strconv.Atoi(value) if asInt > 0 && err == nil { config.IntraRefreshPeriod = value } case "HorizontalFlip": switch value { case "Yes": config.HorizontalFlip = revid.Yes case "No": config.HorizontalFlip = revid.No } case "VerticalFlip": switch value { case "Yes": config.VerticalFlip = revid.Yes case "No": config.VerticalFlip = revid.No } } } startRevid() }