├── LICENSE ├── README.md ├── awbType.go ├── colourFx.go ├── dynamicRange.go ├── expostureType.go ├── imgEffectType.go ├── meteringType.go ├── piCamera.go ├── profileType.go ├── raspividArgs.go ├── regionOfIntrest.go ├── start.go └── start_pi.go /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2017, Technomancers 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # piCamera [![GoDoc](https://godoc.org/github.com/technomancers/piCamera?status.svg)](https://godoc.org/github.com/technomancers/piCamera) 2 | 3 | This package is a wrapper for the `raspivid` command on the Raspberry Pi. To make development easier on a PC, there is are different `Start()` methods depending on what is compiled. One is for the Raspberry Pi and the other is for everything else. 4 | 5 | ## Installation 6 | 7 | Since this package depends on a Raspberry Pi only command there is an extra flag needed to build this package for the Raspberry Pi. 8 | 9 | ```sh 10 | env GOOS=linux GOARCH=arm GOARM=7 go build -tags pi -a . 11 | ``` 12 | 13 | Make note of the `-tags pi` on build. Any `main` package that has any dependency to this package should have that flag so that this package is built correctly for the Raspberry Pi. 14 | -------------------------------------------------------------------------------- /awbType.go: -------------------------------------------------------------------------------- 1 | //Copyright (c) 2017, Technomancers. All rights reserved. 2 | //Use of this source code is governed by a BSD-style 3 | //license that can be found in the LICENSE file. 4 | 5 | package piCamera 6 | 7 | import ( 8 | "fmt" 9 | ) 10 | 11 | //AWBType is for setting the Automatic White Balance setting. 12 | type AWBType int 13 | 14 | const ( 15 | //AwbAuto is for automatic white balance 16 | AwbAuto AWBType = iota 17 | //AwbOff is for turning automatic white balance off 18 | AwbOff 19 | //AwbSun is for sunny mode 20 | AwbSun 21 | //AwbCloud is for cloudy mode 22 | AwbCloud 23 | //AwbShade is for shade mode 24 | AwbShade 25 | //AwbTungsten tungsten lighting mode 26 | AwbTungsten 27 | //AwbFluorescent fluorescent lighting mode 28 | AwbFluorescent 29 | //AwbIncandescent incandescent lighting mode 30 | AwbIncandescent 31 | //AwbFlash flash mode 32 | AwbFlash 33 | //AwbHorizon horizon mode 34 | AwbHorizon 35 | ) 36 | 37 | //Convert takes the type and returns the string representation of that value. 38 | //Returns true as well if it is the default value. 39 | func (t AWBType) Convert() (string, bool) { //nolint: gocyclo 40 | switch t { 41 | case AwbAuto: 42 | return "auto", true 43 | case AwbOff: 44 | return "off", false 45 | case AwbSun: 46 | return "sun", false 47 | case AwbCloud: 48 | return "cloud", false 49 | case AwbShade: 50 | return "shade", false 51 | case AwbTungsten: 52 | return "tungsten", false 53 | case AwbFluorescent: 54 | return "fluorescent", false 55 | case AwbIncandescent: 56 | return "incandescent", false 57 | case AwbFlash: 58 | return "flash", false 59 | case AwbHorizon: 60 | return "horizon", false 61 | default: 62 | return "", true 63 | } 64 | } 65 | 66 | //AWBGains sets the blue and red gains to be applied when AWBOff is set. 67 | type AWBGains struct { 68 | b float32 69 | r float32 70 | } 71 | 72 | //NewAWBGains creates gains to set to Red and Blue. 73 | //Values are multiplied to the values so 1.5 is 150% and .5 is 50%. 74 | func NewAWBGains(blue, red float32) *AWBGains { 75 | return &AWBGains{ 76 | b: blue, 77 | r: red, 78 | } 79 | } 80 | 81 | //Convert takes the type and returns the string representation of that value. 82 | func (t *AWBGains) Convert() string { 83 | return fmt.Sprintf("%.2f,%.2f", t.b, t.r) 84 | } 85 | -------------------------------------------------------------------------------- /colourFx.go: -------------------------------------------------------------------------------- 1 | //Copyright (c) 2017, Technomancers. All rights reserved. 2 | //Use of this source code is governed by a BSD-style 3 | //license that can be found in the LICENSE file. 4 | 5 | package piCamera 6 | 7 | import ( 8 | "fmt" 9 | ) 10 | 11 | //ColourEffect is used fo setting which color effect one wants. 12 | type ColourEffect struct { 13 | u int 14 | v int 15 | } 16 | 17 | //NewColourEffect creates a colour effect of the U and Y channel of the image. 18 | //Values should be in the range from (0, 255). 19 | func NewColourEffect(u, v int) *ColourEffect { 20 | return &ColourEffect{ 21 | u: u, 22 | v: v, 23 | } 24 | } 25 | 26 | //Convert takes the type and returns the string representation of that value. 27 | func (t *ColourEffect) Convert() string { 28 | return fmt.Sprintf("%d:%d", t.u, t.v) 29 | } 30 | -------------------------------------------------------------------------------- /dynamicRange.go: -------------------------------------------------------------------------------- 1 | //Copyright (c) 2017, Technomancers. All rights reserved. 2 | //Use of this source code is governed by a BSD-style 3 | //license that can be found in the LICENSE file. 4 | 5 | package piCamera 6 | 7 | //DRCType is to set the dynamic range compression 8 | type DRCType int 9 | 10 | const ( 11 | //DRCNone is so this package can use whatever the default is 12 | DRCNone DRCType = iota 13 | //DRCOff turns of DRC 14 | DRCOff 15 | //DRCLow compress the range slightly 16 | DRCLow 17 | //DRCMedium compress the range more 18 | DRCMedium 19 | //DRCHigh compress the range even more 20 | DRCHigh 21 | ) 22 | 23 | //Convert takes the type and returns the string representation of that value. 24 | //Returns true as well if it is the default value. 25 | func (t DRCType) Convert() (string, bool) { 26 | switch t { 27 | case DRCOff: 28 | return "off", false 29 | case DRCLow: 30 | return "low", false 31 | case DRCMedium: 32 | return "medium", false 33 | case DRCHigh: 34 | return "high", false 35 | case DRCNone: 36 | fallthrough 37 | default: 38 | return "", true 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /expostureType.go: -------------------------------------------------------------------------------- 1 | //Copyright (c) 2017, Technomancers. All rights reserved. 2 | //Use of this source code is governed by a BSD-style 3 | //license that can be found in the LICENSE file. 4 | 5 | package piCamera 6 | 7 | //ExposureType is for setting the exposure mode. 8 | type ExposureType int 9 | 10 | const ( 11 | //ExpNone is used to tell this package to use whatever the default is. 12 | ExpNone ExposureType = iota 13 | //ExpAuto use automatic exposure mode 14 | ExpAuto 15 | //ExpNight select setting for night shooting 16 | ExpNight 17 | //ExpBacklight select setting for backlit subject 18 | ExpBacklight 19 | //ExpSpotlight select setting for spotlit subject 20 | ExpSpotlight 21 | //ExpSports select setting for sports 22 | ExpSports 23 | //ExpSnow select setting optimised for snowy scenery 24 | ExpSnow 25 | //ExpBeach select setting optimised for beach 26 | ExpBeach 27 | //ExpVerylong select setting for long exposures 28 | ExpVerylong 29 | //ExpFixedfps constrain fps to a fixed value 30 | ExpFixedfps 31 | //ExpAntishake turns on antishake mode 32 | ExpAntishake 33 | //ExpFireworks select setting optimised for fireworks 34 | ExpFireworks 35 | ) 36 | 37 | //Convert takes the type and returns the string representation of that value. 38 | //Returns true as well if it is the default value. 39 | func (t ExposureType) Convert() (string, bool) { //nolint: gocyclo 40 | switch t { 41 | case ExpAuto: 42 | return "auto", false 43 | case ExpNight: 44 | return "night", false 45 | case ExpBacklight: 46 | return "backlight", false 47 | case ExpSpotlight: 48 | return "spotlight", false 49 | case ExpSports: 50 | return "sports", false 51 | case ExpSnow: 52 | return "snow", false 53 | case ExpBeach: 54 | return "beach", false 55 | case ExpVerylong: 56 | return "verylong", false 57 | case ExpFixedfps: 58 | return "fixedfps", false 59 | case ExpAntishake: 60 | return "antishake", false 61 | case ExpFireworks: 62 | return "fireworks", false 63 | case ExpNone: 64 | fallthrough 65 | default: 66 | return "", true 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /imgEffectType.go: -------------------------------------------------------------------------------- 1 | //Copyright (c) 2017, Technomancers. All rights reserved. 2 | //Use of this source code is governed by a BSD-style 3 | //license that can be found in the LICENSE file. 4 | 5 | package piCamera 6 | 7 | //ImgEffectType is used for setting the image effect to use. 8 | type ImgEffectType int 9 | 10 | const ( 11 | //ImfxNone no effect 12 | ImfxNone ImgEffectType = iota 13 | //ImfxNegative invert the image colours 14 | ImfxNegative 15 | //ImfxSolarise solarise the image 16 | ImfxSolarise 17 | //ImfxPosterise posterise the image 18 | ImfxPosterise 19 | //ImfxWhiteboard whiteboard effect 20 | ImfxWhiteboard 21 | //ImfxBlackboard blackboard effect 22 | ImfxBlackboard 23 | //ImfxSketch sketch effect 24 | ImfxSketch 25 | //ImfxDenoise denoise the image 26 | ImfxDenoise 27 | //ImfxEmboss the image 28 | ImfxEmboss 29 | //ImfxOilpaint oil paint effect 30 | ImfxOilpaint 31 | //ImfxHatch hatch sektch effect 32 | ImfxHatch 33 | //ImfxGPen graphite sketch effect 34 | ImfxGPen 35 | //ImfxPastel pastel effect 36 | ImfxPastel 37 | //ImfxWatercolour watercolour effect 38 | ImfxWatercolour 39 | //ImfxFilm film grain effect 40 | ImfxFilm 41 | //ImfxBlur blur the image 42 | ImfxBlur 43 | //ImfxSaturation colour saturate the image 44 | ImfxSaturation 45 | ) 46 | 47 | //Convert takes the type and returns the string representation of that value. 48 | //Returns true as well if it is the default value. 49 | func (t ImgEffectType) Convert() (string, bool) { //nolint: gocyclo 50 | switch t { 51 | case ImfxNone: 52 | return "none", true 53 | case ImfxNegative: 54 | return "negative", false 55 | case ImfxSolarise: 56 | return "solarise", false 57 | case ImfxPosterise: 58 | return "posterise", false 59 | case ImfxWhiteboard: 60 | return "whiteboard", false 61 | case ImfxBlackboard: 62 | return "blackboard", false 63 | case ImfxSketch: 64 | return "sketch", false 65 | case ImfxDenoise: 66 | return "denoise", false 67 | case ImfxEmboss: 68 | return "emboss", false 69 | case ImfxOilpaint: 70 | return "oilpaint", false 71 | case ImfxHatch: 72 | return "hatch", false 73 | case ImfxGPen: 74 | return "gpen", false 75 | case ImfxPastel: 76 | return "pastel", false 77 | case ImfxWatercolour: 78 | return "watercolour", false 79 | case ImfxFilm: 80 | return "film", false 81 | case ImfxBlur: 82 | return "blur", false 83 | case ImfxSaturation: 84 | return "saturation", false 85 | default: 86 | return "", true 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /meteringType.go: -------------------------------------------------------------------------------- 1 | //Copyright (c) 2017, Technomancers. All rights reserved. 2 | //Use of this source code is governed by a BSD-style 3 | //license that can be found in the LICENSE file. 4 | 5 | package piCamera 6 | 7 | //MeteringType is used for setting the Metering Mode. 8 | type MeteringType int 9 | 10 | const ( 11 | //MeterNone tell this package to use whatever the default is 12 | MeterNone MeteringType = iota 13 | //MeterAverage average the whole frame for metering 14 | MeterAverage 15 | //MeterSpot use spot metering 16 | MeterSpot 17 | //MeterBacklit will assume a backlit image 18 | MeterBacklit 19 | //MeterMatrix use matrix metering 20 | MeterMatrix 21 | ) 22 | 23 | //Convert takes the type and returns the string representation of that value. 24 | //Returns true as well if it is the default value. 25 | func (t MeteringType) Convert() (string, bool) { 26 | switch t { 27 | case MeterAverage: 28 | return "average", false 29 | case MeterSpot: 30 | return "spot", false 31 | case MeterBacklit: 32 | return "backlit", false 33 | case MeterMatrix: 34 | return "matrix", false 35 | case MeterNone: 36 | fallthrough 37 | default: 38 | return "", true 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /piCamera.go: -------------------------------------------------------------------------------- 1 | //Copyright (c) 2017, Technomancers. All rights reserved. 2 | //Use of this source code is governed by a BSD-style 3 | //license that can be found in the LICENSE file. 4 | 5 | /*Package piCamera is a simple wrapper for raspivid. 6 | 7 | There is a non-RaspberryPi version that is used for local development. 8 | This can become handy when the IDE does not know how to handle certain features. 9 | */ 10 | package piCamera 11 | 12 | import ( 13 | "context" 14 | "io" 15 | "os/exec" 16 | 17 | "errors" 18 | "os" 19 | "sync" 20 | ) 21 | 22 | //nolint: varcheck, unused 23 | var jpgMagic = []byte{0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46} //This is covered here https://asecuritysite.com/forensics/jpeg 24 | 25 | //PiCamera creates a way for code to be able to pull images from the camera live. 26 | //You must start eh raspivid explicitly first but once started, PiCamera will have the latest image available to view. 27 | // 28 | //PiCamera is thread safe so many calls to GetFrame() will not break. 29 | //Be careful as the more calls to GetFrame() the slower GetFrame() may become due to all the read locks. 30 | type PiCamera struct { 31 | rwMutext *sync.RWMutex 32 | ctx context.Context 33 | cancel context.CancelFunc 34 | command *exec.Cmd 35 | stdOut io.ReadCloser 36 | latestImg []byte 37 | args *RaspividArgs 38 | } 39 | 40 | //New creates an instance of PiCamera. 41 | //Width and Height are for the image size. 42 | //ctx is the parent context. If nil a background context will be created. 43 | // 44 | //This creates the command raspivid with the appropriate settings. 45 | //The stdErr of the command is redirected to os.Stderr so that one may see why the command may have failed. 46 | func New(parentCtx context.Context, args *RaspividArgs) (*PiCamera, error) { 47 | var ctx context.Context 48 | var cancel context.CancelFunc 49 | if parentCtx == nil { 50 | ctx, cancel = context.WithCancel(context.Background()) 51 | } else { 52 | ctx, cancel = context.WithCancel(parentCtx) 53 | } 54 | cmd := createCommand(ctx, args) 55 | stdOut, err := cmd.StdoutPipe() 56 | if err != nil { 57 | cancel() 58 | return nil, err 59 | } 60 | cmd.Stderr = os.Stderr 61 | return &PiCamera{ 62 | ctx: ctx, 63 | cancel: cancel, 64 | command: cmd, 65 | stdOut: stdOut, 66 | rwMutext: new(sync.RWMutex), 67 | }, nil 68 | } 69 | 70 | //GetFrame returns the latest frame from raspivid. 71 | //If there is no frame available it will throw an error. 72 | func (pc *PiCamera) GetFrame() ([]byte, error) { 73 | if pc.ctx.Err() != nil { 74 | return nil, pc.ctx.Err() 75 | } 76 | pc.rwMutext.RLock() 77 | defer pc.rwMutext.RUnlock() 78 | if pc.latestImg == nil { 79 | return nil, errors.New("Latest Image is empty") 80 | } 81 | return pc.latestImg, nil 82 | } 83 | 84 | //Stop the raspivid command. 85 | //Safely stop all the commands and routines with this. 86 | func (pc *PiCamera) Stop() { 87 | if pc.ctx.Err() == nil { 88 | pc.cancel() 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /profileType.go: -------------------------------------------------------------------------------- 1 | //Copyright (c) 2017, Technomancers. All rights reserved. 2 | //Use of this source code is governed by a BSD-style 3 | //license that can be found in the LICENSE file. 4 | 5 | package piCamera 6 | 7 | //ProfileType sets the H264 profile to be used for the encoding. 8 | type ProfileType int 9 | 10 | const ( 11 | //ProfileNone tells this package to use whatever the default is. 12 | ProfileNone ProfileType = iota 13 | //ProfileBaseline is for the baseline profile 14 | ProfileBaseline 15 | //ProfileMain is for the main profile 16 | ProfileMain 17 | //ProfileHigh is for a high profile 18 | ProfileHigh 19 | ) 20 | 21 | //Convert takes the type and returns the string representation of that value. 22 | //Returns true as well if it is the default value. 23 | func (t ProfileType) Convert() (string, bool) { 24 | switch t { 25 | case ProfileBaseline: 26 | return "baseline", false 27 | case ProfileMain: 28 | return "main", false 29 | case ProfileHigh: 30 | return "high", false 31 | case ProfileNone: 32 | fallthrough 33 | default: 34 | return "", true 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /raspividArgs.go: -------------------------------------------------------------------------------- 1 | //Copyright (c) 2017, Technomancers. All rights reserved. 2 | //Use of this source code is governed by a BSD-style 3 | //license that can be found in the LICENSE file. 4 | 5 | package piCamera 6 | 7 | import ( 8 | "context" 9 | "os/exec" 10 | "strconv" 11 | ) 12 | 13 | //Command has a non zero default so these are the values used to make up for it 14 | const ( 15 | defBrightness = 50 16 | defMode = -1 17 | ) 18 | 19 | //RaspividArgs are arguments used to set camera settings for the desired output 20 | //https://www.raspberrypi.org/documentation/raspbian/applications/camera.md 21 | type RaspividArgs struct { 22 | HorizFlip bool // flip the image horizontally 23 | VertFlip bool // flip the camera vertically 24 | VideoStable bool // try to stableize the video 25 | InsertHeaders bool // insert pps, sps headers to every I-Frame 26 | Width int // width of the image 27 | Height int // height of the image 28 | Sharpness int // change the sharpness of the camera (-100 , 100 DEF 0) 29 | Contrast int // change the contrast of the camera (-100 , 100 DEF 0) 30 | Brightness int // change the brightness of the camera (0 , 100 DEF 50) 31 | Saturation int // change the saturation of the camera (-100 , 100 DEF 0) 32 | ISO int // change the sensitivity the camera is to light (100 , 800 DEF 100) 33 | EV int // Slightly under or over expose the camera (-10 , 10 DEF 0) 34 | Bitrate int // set the bitrate in bits per second. Max is 25000000 35 | FPS int // set the frames per second (2 , 30) 36 | IntraRate int // set number of frames before next intra frame 37 | Quantization int // set Quantization parameter 38 | Mode int // set the mode of the camera by checking the documentation 39 | ShutterSpeed int // set the shutter speed in microseconds (Max 6000000) 40 | Rotation int // set the rotation of the image. (0, 90, 180, 270) 41 | Annotate string // annotate the image according to the documentation 42 | AnnotateExtra string // annotate the image according to the documentation 43 | ExposureMode ExposureType // set which mode to use for exposure 44 | AWB AWBType // set the automatic white balance mode 45 | ImageFx ImgEffectType // set the image effect 46 | Metering MeteringType // ste the metering mode 47 | DRC DRCType // set the dynamic range compression 48 | AWBGains *AWBGains // set the AWBGains when AWB is off 49 | ROI *RegionOfIntrest // set the cameras region of interest 50 | ColourFx *ColourEffect // set the color effects to an image 51 | 52 | Profile ProfileType // set the profile type 53 | } 54 | 55 | //NewArgs returns a RaspividArgs with the default settings 56 | func NewArgs() *RaspividArgs { 57 | return &RaspividArgs{ 58 | Brightness: defBrightness, 59 | Mode: defMode, 60 | } 61 | } 62 | 63 | //nolint: gocyclo 64 | func createCommand(ctx context.Context, args *RaspividArgs) *exec.Cmd { 65 | cmd := exec.CommandContext(ctx, "raspivid", "-cd", "MJPEG", "-t", "0") //nolint: gas 66 | var final []string 67 | if args.Width != 0 { 68 | final = append(final, "-w", strconv.Itoa(args.Width)) 69 | } 70 | if args.Height != 0 { 71 | final = append(final, "-h", strconv.Itoa(args.Height)) 72 | } 73 | if args.HorizFlip { 74 | final = append(final, "-hf") 75 | } 76 | if args.VertFlip { 77 | final = append(final, "-vf") 78 | } 79 | if args.Sharpness != 0 { 80 | final = append(final, "-sh", strconv.Itoa(args.Sharpness)) 81 | } 82 | if args.Contrast != 0 { 83 | final = append(final, "-co", strconv.Itoa(args.Contrast)) 84 | } 85 | if args.Brightness != defBrightness { 86 | final = append(final, "-br", strconv.Itoa(args.Brightness)) 87 | } 88 | if args.Saturation != 0 { 89 | final = append(final, "-sa", strconv.Itoa(args.Saturation)) 90 | } 91 | if args.ISO != 0 { 92 | final = append(final, "-ISO", strconv.Itoa(args.ISO)) 93 | } 94 | if args.VideoStable { 95 | final = append(final, "-vs") 96 | } 97 | if args.EV != 0 { 98 | final = append(final, "-ev", strconv.Itoa(args.EV)) 99 | } 100 | if mode, def := args.ExposureMode.Convert(); !def { 101 | final = append(final, "-ex", mode) 102 | } 103 | if mode, def := args.AWB.Convert(); !def { 104 | final = append(final, "-awb", mode) 105 | } 106 | if mode, def := args.ImageFx.Convert(); !def { 107 | final = append(final, "-ifx", mode) 108 | } 109 | if args.ColourFx != nil { 110 | final = append(final, "-cfx", args.ColourFx.Convert()) 111 | } 112 | if mode, def := args.Metering.Convert(); !def { 113 | final = append(final, "-mm", mode) 114 | } 115 | if args.Rotation != 0 { 116 | final = append(final, "-rot", strconv.Itoa(args.Rotation)) 117 | } 118 | if args.ROI != nil { 119 | final = append(final, "-roi", args.ROI.Convert()) 120 | } 121 | if args.ShutterSpeed != 0 { 122 | final = append(final, "-ss", strconv.Itoa(args.ShutterSpeed)) 123 | } 124 | if mode, def := args.DRC.Convert(); !def { 125 | final = append(final, "-drc", mode) 126 | } 127 | if args.AWBGains != nil { 128 | final = append(final, "-awbg", args.AWBGains.Convert()) 129 | } 130 | if args.Mode != defMode { 131 | final = append(final, "-md", strconv.Itoa(args.Mode)) 132 | } 133 | if args.AnnotateExtra != "" { 134 | final = append(final, "-ae", args.AnnotateExtra) 135 | } 136 | if args.Annotate != "" { 137 | final = append(final, "-a", args.Annotate) 138 | } 139 | if args.Bitrate != 0 { 140 | final = append(final, "-b", strconv.Itoa(args.Bitrate)) 141 | } 142 | if args.FPS != 0 { 143 | final = append(final, "-fps", strconv.Itoa(args.FPS)) 144 | } 145 | if args.IntraRate != 0 { 146 | final = append(final, "-g", strconv.Itoa(args.IntraRate)) 147 | } 148 | if args.Quantization != 0 { 149 | final = append(final, "-qp", strconv.Itoa(args.Quantization)) 150 | } 151 | if mode, def := args.Profile.Convert(); !def { 152 | final = append(final, "-pf", mode) 153 | } 154 | if args.InsertHeaders { 155 | final = append(final, "-ih") 156 | } 157 | final = append(final, "-o", "-") 158 | cmd.Args = append(cmd.Args, final...) 159 | return cmd 160 | } 161 | -------------------------------------------------------------------------------- /regionOfIntrest.go: -------------------------------------------------------------------------------- 1 | //Copyright (c) 2017, Technomancers. All rights reserved. 2 | //Use of this source code is governed by a BSD-style 3 | //license that can be found in the LICENSE file. 4 | 5 | package piCamera 6 | 7 | import ( 8 | "fmt" 9 | ) 10 | 11 | //RegionOfIntrest is used to set the cameras area to be used as the source. 12 | type RegionOfIntrest struct { 13 | tlX float32 14 | tlY float32 15 | w float32 16 | h float32 17 | } 18 | 19 | //NewROI creates a new Region of Interest. 20 | //tlx and tly are the top left x and y of ROI. 21 | //w and h are the width and height of the ROI. 22 | //All points should be normalized from 0.0 - 1.0. 23 | func NewROI(tlx, tly, w, h float32) *RegionOfIntrest { 24 | return &RegionOfIntrest{ 25 | tlX: tlx, 26 | tlY: tly, 27 | w: w, 28 | h: h, 29 | } 30 | } 31 | 32 | //Convert takes the type and returns the string representation of that value. 33 | func (t *RegionOfIntrest) Convert() string { 34 | return fmt.Sprintf("%.2f,%.2f,%.2f,%.2f", t.tlX, t.tlY, t.w, t.h) 35 | } 36 | -------------------------------------------------------------------------------- /start.go: -------------------------------------------------------------------------------- 1 | // +build !linux !arm !pi 2 | 3 | //Copyright (c) 2017, Technomancers. All rights reserved. 4 | //Use of this source code is governed by a BSD-style 5 | //license that can be found in the LICENSE file. 6 | 7 | package piCamera 8 | 9 | import ( 10 | "image" 11 | "image/draw" 12 | 13 | "bytes" 14 | 15 | "image/jpeg" 16 | 17 | "github.com/golang/freetype" 18 | "github.com/golang/freetype/truetype" 19 | "golang.org/x/image/font" 20 | "golang.org/x/image/font/gofont/goregular" 21 | ) 22 | 23 | //Start raspivid in the background. 24 | //Also logs the PID to os.stdOut. 25 | // 26 | //This is the function that is built seperatly depending on if you are building for the Raspberry Pi or not. 27 | func (pc *PiCamera) Start() error { 28 | f, err := truetype.Parse(goregular.TTF) 29 | if err != nil { 30 | return err 31 | } 32 | fg, bg := image.White, image.Black 33 | rgba := image.NewRGBA(image.Rect(0, 0, pc.args.Width, pc.args.Height)) 34 | draw.Draw(rgba, rgba.Bounds(), bg, image.ZP, draw.Src) 35 | c := freetype.NewContext() 36 | c.SetDPI(72) 37 | c.SetFont(f) 38 | c.SetFontSize(20) 39 | c.SetClip(rgba.Bounds()) 40 | c.SetDst(rgba) 41 | c.SetSrc(fg) 42 | c.SetHinting(font.HintingNone) 43 | str := "You are not running running on a pi." 44 | base := int(float64(c.PointToFixed(20)>>6) / 2) 45 | pt := freetype.Pt((pc.args.Width/2)-(len(str)*5), (pc.args.Height/2)-base) 46 | _, err = c.DrawString(str, pt) 47 | if err != nil { 48 | return err 49 | } 50 | buff := new(bytes.Buffer) 51 | err = jpeg.Encode(buff, rgba, &jpeg.Options{Quality: 70}) 52 | if err != nil { 53 | return err 54 | } 55 | pc.latestImg = buff.Bytes() 56 | return nil 57 | } 58 | -------------------------------------------------------------------------------- /start_pi.go: -------------------------------------------------------------------------------- 1 | // +build linux,arm,pi 2 | 3 | //Copyright (c) 2017, Technomancers. All rights reserved. 4 | //Use of this source code is governed by a BSD-style 5 | //license that can be found in the LICENSE file. 6 | 7 | package piCamera 8 | 9 | import ( 10 | "bytes" 11 | "log" 12 | ) 13 | 14 | //Start raspivid in the background. 15 | //Also logs the PID to os.stdOut. 16 | // 17 | //This is the function that is built seperatly depending on if you are building for the Raspberry Pi or not. 18 | func (pc *PiCamera) Start() error { 19 | err := pc.command.Start() 20 | go pc.updateLatest() 21 | log.Printf("PiCamera running PID: %d", pc.command.Process.Pid) 22 | return err 23 | } 24 | 25 | func (pc *PiCamera) updateLatest() { 26 | readBuff := make([]byte, 4096) //Buffer of the currently read bytes (4 kilobytes) 27 | var work = make([]byte, len(jpgMagic)) //This is the currently working bytes in process data. Must be outside function as it can carry over calls 28 | var buffer = new(bytes.Buffer) //The new image buffer. The one currently being processed 29 | for { 30 | select { 31 | case <-pc.ctx.Done(): 32 | break 33 | default: 34 | n, err := pc.stdOut.Read(readBuff) 35 | if err != nil { 36 | log.Printf("Reading from raspivid stdOut error: %v", err) 37 | break 38 | } 39 | start := 0 //This is where the image starts if this data splits images 40 | //Read through the data 41 | for i := 0; i < n; i++ { 42 | //add byte to working bytes 43 | work = append(work[1:], readBuff[i]) 44 | //If we are at the start of a new image 45 | if bytes.Compare(work, jpgMagic) == 0 { 46 | buffer.Write(readBuff[start:i]) //write what is left of the old image 47 | if buffer.Len() > 0 { 48 | end := buffer.Len() - len(jpgMagic) + 1 //figure out where the end of the previous image was 49 | image := buffer.Bytes()[:end] 50 | cpyImage := make([]byte, len(image)) 51 | copy(cpyImage, image) 52 | rest := buffer.Bytes()[end:] 53 | //write the image to the latest 54 | pc.rwMutext.Lock() 55 | pc.latestImg = cpyImage 56 | pc.rwMutext.Unlock() 57 | buffer.Reset() //Clear out the buffer 58 | buffer.Write(rest) //Include the partial image that was left back into the buffer 59 | start = i //make sure to change I so that the rest of the readBuffer is cleared correctly 60 | } 61 | } 62 | } 63 | buffer.Write(readBuff[start:n]) //write to the buffer readBuffer depending on where the start was 64 | } 65 | } 66 | } 67 | --------------------------------------------------------------------------------