Add some options

This commit is contained in:
Milan Nikolic
2025-06-14 01:45:32 +02:00
parent fd5cb861cd
commit 7e4e58029a
15 changed files with 128 additions and 96 deletions

View File

@@ -51,12 +51,18 @@ Usage: cam2ip [<flags>]
Frame width [CAM2IP_WIDTH] (default "640") Frame width [CAM2IP_WIDTH] (default "640")
--height --height
Frame height [CAM2IP_HEIGHT] (default "480") Frame height [CAM2IP_HEIGHT] (default "480")
--quality
Image quality [CAM2IP_QUALITY] (default "75")
--rotate --rotate
Rotate image, valid values are 90, 180, 270 [CAM2IP_ROTATE] (default "0") Rotate image, valid values are 90, 180, 270 [CAM2IP_ROTATE] (default "0")
--flip
Flip image, valid values are horizontal and vertical [CAM2IP_FLIP] (default "")
--no-webgl --no-webgl
Disable WebGL drawing of images (html handler) [CAM2IP_NO_WEBGL] (default "false") Disable WebGL drawing of image (html handler) [CAM2IP_NO_WEBGL] (default "false")
--timestamp --timestamp
Draws timestamp on images [CAM2IP_TIMESTAMP] (default "false") Draws timestamp on image [CAM2IP_TIMESTAMP] (default "false")
--time-format
Time format [CAM2IP_TIME_FORMAT] (default "2006-01-02 15:04:05")
--bind-addr --bind-addr
Bind address [CAM2IP_BIND_ADDR] (default ":56000") Bind address [CAM2IP_BIND_ADDR] (default ":56000")
--htpasswd-file --htpasswd-file

View File

@@ -2,9 +2,11 @@ package camera
// Options . // Options .
type Options struct { type Options struct {
Index int Index int
Rotate int Rotate int
Width float64 Flip string
Height float64 Width float64
Timestamp bool Height float64
Timestamp bool
TimeFormat string
} }

View File

@@ -92,6 +92,10 @@ func (c *Camera) Read() (img image.Image, err error) {
img = im.Rotate(img, c.opts.Rotate) img = im.Rotate(img, c.opts.Rotate)
} }
if c.opts.Flip != "" {
img = im.Flip(img, c.opts.Flip)
}
if c.opts.Timestamp { if c.opts.Timestamp {
img, err = im.Timestamp(img, "") img, err = im.Timestamp(img, "")
} }

View File

@@ -69,6 +69,10 @@ func (c *Camera) Read() (img image.Image, err error) {
img = im.Rotate(img, c.opts.Rotate) img = im.Rotate(img, c.opts.Rotate)
} }
if c.opts.Flip != "" {
img = im.Flip(img, c.opts.Flip)
}
if c.opts.Timestamp { if c.opts.Timestamp {
img, err = im.Timestamp(img, "") img, err = im.Timestamp(img, "")
} }

View File

@@ -34,11 +34,6 @@ func New(opts Options) (camera *Camera, err error) {
camera.opts = opts camera.opts = opts
camera.className = "capWindowClass" camera.className = "capWindowClass"
camera.instance, err = getModuleHandle()
if err != nil {
return
}
camera.frame = image.NewRGBA(image.Rect(0, 0, int(camera.opts.Width), int(camera.opts.Height))) camera.frame = image.NewRGBA(image.Rect(0, 0, int(camera.opts.Width), int(camera.opts.Height)))
go func(c *Camera) { go func(c *Camera) {
@@ -56,12 +51,18 @@ func New(opts Options) (camera *Camera, err error) {
return 0 return 0
} }
c.instance, err = getModuleHandle()
if err != nil {
return
}
err = registerClass(c.className, c.instance, fn) err = registerClass(c.className, c.instance, fn)
if err != nil { if err != nil {
return return
} }
hwnd, err := createWindow(0, c.className, "", wsOverlappedWindow, cwUseDefault, cwUseDefault, int64(c.opts.Width)+100, int64(c.opts.Height)+100, 0, 0, c.instance) hwnd, err := createWindow(0, c.className, "", wsOverlappedWindow, cwUseDefault, cwUseDefault,
int64(c.opts.Width)+100, int64(c.opts.Height)+100, 0, 0, c.instance)
if err != nil { if err != nil {
return return
} }
@@ -92,7 +93,18 @@ func New(opts Options) (camera *Camera, err error) {
sendMessage(c.camera, wmCapSetCallbackFrame, 0, syscall.NewCallback(c.callback)) sendMessage(c.camera, wmCapSetCallbackFrame, 0, syscall.NewCallback(c.callback))
messageLoop(c.camera) for {
var msg msgW
ok, _ := getMessage(&msg, hwnd, 0, 0)
if ok {
//translateMessage(&msg)
dispatchMessage(&msg)
} else {
break
}
}
return
}(camera) }(camera)
return return
@@ -139,20 +151,14 @@ func (c *Camera) Read() (img image.Image, err error) {
img = im.Rotate(img, c.opts.Rotate) img = im.Rotate(img, c.opts.Rotate)
} }
if c.opts.Timestamp { if c.opts.Flip != "" {
img, err = im.Timestamp(img, "") img = im.Flip(img, c.opts.Flip)
} }
return if c.opts.Timestamp {
} img, err = im.Timestamp(img, c.opts.TimeFormat)
}
// GetProperty returns the specified camera property.
func (c *Camera) GetProperty(id int) float64 {
return 0
}
// SetProperty sets a camera property.
func (c *Camera) SetProperty(id int, value float64) {
return return
} }
@@ -321,8 +327,9 @@ func destroyWindow(hwnd syscall.Handle) error {
// https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-defwindowprocw // https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-defwindowprocw
func defWindowProc(hwnd syscall.Handle, msg uint32, wparam, lparam uintptr) uintptr { func defWindowProc(hwnd syscall.Handle, msg uint32, wparam, lparam uintptr) uintptr {
ret, _, _ := defWindowProcW.Call(uintptr(hwnd), uintptr(msg), uintptr(wparam), uintptr(lparam)) ret, _, _ := defWindowProcW.Call(uintptr(hwnd), uintptr(msg), wparam, lparam)
return uintptr(ret)
return ret
} }
// https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-dispatchmessagew // https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-dispatchmessagew
@@ -348,6 +355,7 @@ func getMessage(msg *msgW, hwnd syscall.Handle, msgFilterMin, msgFilterMax uint3
// https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-sendmessage // https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-sendmessage
func sendMessage(hwnd syscall.Handle, msg uint32, wparam, lparam uintptr) uintptr { func sendMessage(hwnd syscall.Handle, msg uint32, wparam, lparam uintptr) uintptr {
ret, _, _ := sendMessageW.Call(uintptr(hwnd), uintptr(msg), wparam, lparam, 0, 0) ret, _, _ := sendMessageW.Call(uintptr(hwnd), uintptr(msg), wparam, lparam, 0, 0)
return ret return ret
} }
@@ -375,6 +383,7 @@ func registerClass(className string, instance syscall.Handle, fn interface{}) er
// https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-unregisterclassw // https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-unregisterclassw
func unregisterClass(className string, instance syscall.Handle) bool { func unregisterClass(className string, instance syscall.Handle) bool {
ret, _, _ := unregisterClassW.Call(uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(className))), uintptr(instance)) ret, _, _ := unregisterClassW.Call(uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(className))), uintptr(instance))
return ret != 0 return ret != 0
} }
@@ -388,19 +397,3 @@ func capCreateCaptureWindow(lpszWindowName string, dwStyle, x, y, width, height
return syscall.Handle(ret), nil return syscall.Handle(ret), nil
} }
// messageLoop function
func messageLoop(hwnd syscall.Handle) {
for {
msg := &msgW{}
ok, _ := getMessage(msg, 0, 0, 0)
if ok {
translateMessage(msg)
dispatchMessage(msg)
} else {
break
}
}
return
}

View File

@@ -21,17 +21,21 @@ func main() {
flag.IntVar(&srv.Index, "index", 0, "Camera index [CAM2IP_INDEX]") flag.IntVar(&srv.Index, "index", 0, "Camera index [CAM2IP_INDEX]")
flag.IntVar(&srv.Delay, "delay", 10, "Delay between frames, in milliseconds [CAM2IP_DELAY]") flag.IntVar(&srv.Delay, "delay", 10, "Delay between frames, in milliseconds [CAM2IP_DELAY]")
flag.Float64Var(&srv.FrameWidth, "width", 640, "Frame width [CAM2IP_WIDTH]") flag.Float64Var(&srv.Width, "width", 640, "Frame width [CAM2IP_WIDTH]")
flag.Float64Var(&srv.FrameHeight, "height", 480, "Frame height [CAM2IP_HEIGHT]") flag.Float64Var(&srv.Height, "height", 480, "Frame height [CAM2IP_HEIGHT]")
flag.IntVar(&srv.Quality, "quality", 75, "Image quality [CAM2IP_QUALITY]")
flag.IntVar(&srv.Rotate, "rotate", 0, "Rotate image, valid values are 90, 180, 270 [CAM2IP_ROTATE]") flag.IntVar(&srv.Rotate, "rotate", 0, "Rotate image, valid values are 90, 180, 270 [CAM2IP_ROTATE]")
flag.BoolVar(&srv.NoWebGL, "no-webgl", false, "Disable WebGL drawing of images (html handler) [CAM2IP_NO_WEBGL]") flag.StringVar(&srv.Flip, "flip", "", "Flip image, valid values are horizontal and vertical [CAM2IP_FLIP]")
flag.BoolVar(&srv.Timestamp, "timestamp", false, "Draws timestamp on images [CAM2IP_TIMESTAMP]") flag.BoolVar(&srv.NoWebGL, "no-webgl", false, "Disable WebGL drawing of image (html handler) [CAM2IP_NO_WEBGL]")
flag.BoolVar(&srv.Timestamp, "timestamp", false, "Draws timestamp on image [CAM2IP_TIMESTAMP]")
flag.StringVar(&srv.TimeFormat, "time-format", "2006-01-02 15:04:05", "Time format [CAM2IP_TIME_FORMAT]")
flag.StringVar(&srv.Bind, "bind-addr", ":56000", "Bind address [CAM2IP_BIND_ADDR]") flag.StringVar(&srv.Bind, "bind-addr", ":56000", "Bind address [CAM2IP_BIND_ADDR]")
flag.StringVar(&srv.Htpasswd, "htpasswd-file", "", "Path to htpasswd file, if empty auth is disabled [CAM2IP_HTPASSWD_FILE]") flag.StringVar(&srv.Htpasswd, "htpasswd-file", "", "Path to htpasswd file, if empty auth is disabled [CAM2IP_HTPASSWD_FILE]")
flag.Usage = func() { flag.Usage = func() {
stderr("Usage: %s [<flags>]\n", name) stderr("Usage: %s [<flags>]\n", name)
order := []string{"index", "delay", "width", "height", "rotate", "no-webgl", "timestamp", "bind-addr", "htpasswd-file"} order := []string{"index", "delay", "width", "height", "quality", "rotate", "flip", "no-webgl",
"timestamp", "time-format", "bind-addr", "htpasswd-file"}
for _, name := range order { for _, name := range order {
f := flag.Lookup(name) f := flag.Lookup(name)
@@ -55,11 +59,13 @@ func main() {
} }
cam, err := camera.New(camera.Options{ cam, err := camera.New(camera.Options{
Index: srv.Index, Index: srv.Index,
Rotate: srv.Rotate, Rotate: srv.Rotate,
Width: srv.FrameWidth, Flip: srv.Flip,
Height: srv.FrameHeight, Width: srv.Width,
Timestamp: srv.Timestamp, Height: srv.Height,
Timestamp: srv.Timestamp,
TimeFormat: srv.TimeFormat,
}) })
if err != nil { if err != nil {
stderr("%s\n", err.Error()) stderr("%s\n", err.Error())

View File

@@ -12,11 +12,11 @@ type HTML struct {
} }
// NewHTML returns new HTML handler. // NewHTML returns new HTML handler.
func NewHTML(width, height float64, nogl bool) *HTML { func NewHTML(width, height float64, noWebGL bool) *HTML {
h := &HTML{} h := &HTML{}
tpl := htmlWebGL tpl := htmlWebGL
if nogl { if noWebGL {
tpl = html tpl = html
} }
tpl = strings.Replace(tpl, "{WIDTH}", fmt.Sprintf("%.0f", width), -1) tpl = strings.Replace(tpl, "{WIDTH}", fmt.Sprintf("%.0f", width), -1)

View File

@@ -9,12 +9,13 @@ import (
// JPEG handler. // JPEG handler.
type JPEG struct { type JPEG struct {
reader ImageReader reader ImageReader
quality int
} }
// NewJPEG returns new JPEG handler. // NewJPEG returns new JPEG handler.
func NewJPEG(reader ImageReader) *JPEG { func NewJPEG(reader ImageReader, quality int) *JPEG {
return &JPEG{reader} return &JPEG{reader, quality}
} }
// ServeHTTP handles requests on incoming connections. // ServeHTTP handles requests on incoming connections.
@@ -36,7 +37,7 @@ func (j *JPEG) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return return
} }
err = image.NewEncoder(w).Encode(img) err = image.NewEncoder(w, j.quality).Encode(img)
if err != nil { if err != nil {
log.Printf("jpeg: encode: %v", err) log.Printf("jpeg: encode: %v", err)

View File

@@ -13,13 +13,14 @@ import (
// MJPEG handler. // MJPEG handler.
type MJPEG struct { type MJPEG struct {
reader ImageReader reader ImageReader
delay int delay int
quality int
} }
// NewMJPEG returns new MJPEG handler. // NewMJPEG returns new MJPEG handler.
func NewMJPEG(reader ImageReader, delay int) *MJPEG { func NewMJPEG(reader ImageReader, delay, quality int) *MJPEG {
return &MJPEG{reader, delay} return &MJPEG{reader, delay, quality}
} }
// ServeHTTP handles requests on incoming connections. // ServeHTTP handles requests on incoming connections.
@@ -61,7 +62,7 @@ loop:
continue continue
} }
err = image.NewEncoder(partWriter).Encode(img) err = image.NewEncoder(partWriter, m.quality).Encode(img)
if err != nil { if err != nil {
log.Printf("mjpeg: encode: %v", err) log.Printf("mjpeg: encode: %v", err)
continue continue

View File

@@ -14,13 +14,14 @@ import (
// Socket handler. // Socket handler.
type Socket struct { type Socket struct {
reader ImageReader reader ImageReader
delay int delay int
quality int
} }
// NewSocket returns new socket handler. // NewSocket returns new socket handler.
func NewSocket(reader ImageReader, delay int) *Socket { func NewSocket(reader ImageReader, delay, quality int) *Socket {
return &Socket{reader, delay} return &Socket{reader, delay, quality}
} }
// ServeHTTP handles requests on incoming connections. // ServeHTTP handles requests on incoming connections.
@@ -43,7 +44,7 @@ func (s *Socket) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w := new(bytes.Buffer) w := new(bytes.Buffer)
err = image.NewEncoder(w).Encode(img) err = image.NewEncoder(w, s.quality).Encode(img)
if err != nil { if err != nil {
log.Printf("socket: encode: %v", err) log.Printf("socket: encode: %v", err)
continue continue

View File

@@ -10,18 +10,19 @@ import (
) )
// NewEncoder returns a new Encoder. // NewEncoder returns a new Encoder.
func NewEncoder(w io.Writer) *Encoder { func NewEncoder(w io.Writer, quality int) *Encoder {
return &Encoder{w} return &Encoder{w, quality}
} }
// Encoder struct. // Encoder struct.
type Encoder struct { type Encoder struct {
w io.Writer w io.Writer
quality int
} }
// Encode encodes image to JPEG. // Encode encodes image to JPEG.
func (e Encoder) Encode(img image.Image) error { func (e Encoder) Encode(img image.Image) error {
err := jpeg.Encode(e.w, img, &jpeg.Options{Quality: 75}) err := jpeg.Encode(e.w, img, &jpeg.Options{Quality: e.quality})
if err != nil { if err != nil {
return err return err
} }

View File

@@ -11,19 +11,20 @@ import (
) )
// NewEncoder returns a new Encoder. // NewEncoder returns a new Encoder.
func NewEncoder(w io.Writer) *Encoder { func NewEncoder(w io.Writer, quality int) *Encoder {
return &Encoder{w} return &Encoder{w, quality}
} }
// Encoder struct. // Encoder struct.
type Encoder struct { type Encoder struct {
w io.Writer w io.Writer
quality int
} }
// Encode encodes image to JPEG. // Encode encodes image to JPEG.
func (e Encoder) Encode(img image.Image) error { func (e Encoder) Encode(img image.Image) error {
return jpegli.Encode(e.w, img, &jpegli.EncodingOptions{ return jpegli.Encode(e.w, img, &jpegli.EncodingOptions{
Quality: 75, Quality: e.quality,
ProgressiveLevel: 0, ProgressiveLevel: 0,
ChromaSubsampling: image.YCbCrSubsampleRatio420, ChromaSubsampling: image.YCbCrSubsampleRatio420,
DCTMethod: jpegli.DCTIFast, DCTMethod: jpegli.DCTIFast,

View File

@@ -11,19 +11,20 @@ import (
) )
// NewEncoder returns a new Encoder. // NewEncoder returns a new Encoder.
func NewEncoder(w io.Writer) *Encoder { func NewEncoder(w io.Writer, quality int) *Encoder {
return &Encoder{w} return &Encoder{w, quality}
} }
// Encoder struct. // Encoder struct.
type Encoder struct { type Encoder struct {
w io.Writer w io.Writer
quality int
} }
// Encode encodes image to JPEG. // Encode encodes image to JPEG.
func (e Encoder) Encode(img image.Image) error { func (e Encoder) Encode(img image.Image) error {
return jpeg.Encode(e.w, img, &jpeg.EncoderOptions{ return jpeg.Encode(e.w, img, &jpeg.EncoderOptions{
Quality: 75, Quality: e.quality,
DCTMethod: jpeg.DCTIFast, DCTMethod: jpeg.DCTIFast,
ProgressiveMode: false, ProgressiveMode: false,
OptimizeCoding: false, OptimizeCoding: false,

View File

@@ -24,11 +24,18 @@ func Rotate(img image.Image, angle int) image.Image {
return img return img
} }
func Timestamp(img image.Image, format string) (image.Image, error) { func Flip(img image.Image, dir string) image.Image {
if format == "" { switch dir {
format = "2006-01-02 15:04:05" case "horizontal":
img = transform.FlipH(img)
case "vertical":
img = transform.FlipV(img)
} }
return img
}
func Timestamp(img image.Image, format string) (image.Image, error) {
dimg, ok := img.(draw.Image) dimg, ok := img.(draw.Image)
if !ok { if !ok {
return img, fmt.Errorf("camera: %T is not a drawable image type", img) return img, fmt.Errorf("camera: %T is not a drawable image type", img)

View File

@@ -16,19 +16,23 @@ type Server struct {
Name string Name string
Version string Version string
Bind string
Htpasswd string
Index int Index int
Delay int Delay int
FrameWidth float64 Width float64
FrameHeight float64 Height float64
Rotate int Quality int
Rotate int
Flip string
NoWebGL bool NoWebGL bool
Timestamp bool
Timestamp bool
TimeFormat string
Bind string
Htpasswd string
Reader handlers.ImageReader Reader handlers.ImageReader
} }
@@ -48,10 +52,10 @@ func (s *Server) ListenAndServe() error {
basic = auth.NewBasicAuthenticator(realm, auth.HtpasswdFileProvider(s.Htpasswd)) basic = auth.NewBasicAuthenticator(realm, auth.HtpasswdFileProvider(s.Htpasswd))
} }
http.Handle("/html", newAuthHandler(handlers.NewHTML(s.FrameWidth, s.FrameHeight, s.NoWebGL), basic)) http.Handle("/html", newAuthHandler(handlers.NewHTML(s.Width, s.Height, s.NoWebGL), basic))
http.Handle("/jpeg", newAuthHandler(handlers.NewJPEG(s.Reader), basic)) http.Handle("/jpeg", newAuthHandler(handlers.NewJPEG(s.Reader, s.Quality), basic))
http.Handle("/mjpeg", newAuthHandler(handlers.NewMJPEG(s.Reader, s.Delay), basic)) http.Handle("/mjpeg", newAuthHandler(handlers.NewMJPEG(s.Reader, s.Delay, s.Quality), basic))
http.Handle("/socket", newAuthHandler(handlers.NewSocket(s.Reader, s.Delay), basic)) http.Handle("/socket", newAuthHandler(handlers.NewSocket(s.Reader, s.Delay, s.Quality), basic))
http.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)