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")
--height
Frame height [CAM2IP_HEIGHT] (default "480")
--quality
Image quality [CAM2IP_QUALITY] (default "75")
--rotate
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
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
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 address [CAM2IP_BIND_ADDR] (default ":56000")
--htpasswd-file

View File

@@ -2,9 +2,11 @@ package camera
// Options .
type Options struct {
Index int
Rotate int
Width float64
Height float64
Timestamp bool
Index int
Rotate int
Flip string
Width float64
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)
}
if c.opts.Flip != "" {
img = im.Flip(img, c.opts.Flip)
}
if c.opts.Timestamp {
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)
}
if c.opts.Flip != "" {
img = im.Flip(img, c.opts.Flip)
}
if c.opts.Timestamp {
img, err = im.Timestamp(img, "")
}

View File

@@ -34,11 +34,6 @@ func New(opts Options) (camera *Camera, err error) {
camera.opts = opts
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)))
go func(c *Camera) {
@@ -56,12 +51,18 @@ func New(opts Options) (camera *Camera, err error) {
return 0
}
c.instance, err = getModuleHandle()
if err != nil {
return
}
err = registerClass(c.className, c.instance, fn)
if err != nil {
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 {
return
}
@@ -92,7 +93,18 @@ func New(opts Options) (camera *Camera, err error) {
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)
return
@@ -139,20 +151,14 @@ func (c *Camera) Read() (img image.Image, err error) {
img = im.Rotate(img, c.opts.Rotate)
}
if c.opts.Timestamp {
img, err = im.Timestamp(img, "")
if c.opts.Flip != "" {
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
}
@@ -321,8 +327,9 @@ func destroyWindow(hwnd syscall.Handle) error {
// https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-defwindowprocw
func defWindowProc(hwnd syscall.Handle, msg uint32, wparam, lparam uintptr) uintptr {
ret, _, _ := defWindowProcW.Call(uintptr(hwnd), uintptr(msg), uintptr(wparam), uintptr(lparam))
return uintptr(ret)
ret, _, _ := defWindowProcW.Call(uintptr(hwnd), uintptr(msg), wparam, lparam)
return ret
}
// 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
func sendMessage(hwnd syscall.Handle, msg uint32, wparam, lparam uintptr) uintptr {
ret, _, _ := sendMessageW.Call(uintptr(hwnd), uintptr(msg), wparam, lparam, 0, 0)
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
func unregisterClass(className string, instance syscall.Handle) bool {
ret, _, _ := unregisterClassW.Call(uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(className))), uintptr(instance))
return ret != 0
}
@@ -388,19 +397,3 @@ func capCreateCaptureWindow(lpszWindowName string, dwStyle, x, y, width, height
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.Delay, "delay", 10, "Delay between frames, in milliseconds [CAM2IP_DELAY]")
flag.Float64Var(&srv.FrameWidth, "width", 640, "Frame width [CAM2IP_WIDTH]")
flag.Float64Var(&srv.FrameHeight, "height", 480, "Frame height [CAM2IP_HEIGHT]")
flag.Float64Var(&srv.Width, "width", 640, "Frame width [CAM2IP_WIDTH]")
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.BoolVar(&srv.NoWebGL, "no-webgl", false, "Disable WebGL drawing of images (html handler) [CAM2IP_NO_WEBGL]")
flag.BoolVar(&srv.Timestamp, "timestamp", false, "Draws timestamp on images [CAM2IP_TIMESTAMP]")
flag.StringVar(&srv.Flip, "flip", "", "Flip image, valid values are horizontal and vertical [CAM2IP_FLIP]")
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.Htpasswd, "htpasswd-file", "", "Path to htpasswd file, if empty auth is disabled [CAM2IP_HTPASSWD_FILE]")
flag.Usage = func() {
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 {
f := flag.Lookup(name)
@@ -55,11 +59,13 @@ func main() {
}
cam, err := camera.New(camera.Options{
Index: srv.Index,
Rotate: srv.Rotate,
Width: srv.FrameWidth,
Height: srv.FrameHeight,
Timestamp: srv.Timestamp,
Index: srv.Index,
Rotate: srv.Rotate,
Flip: srv.Flip,
Width: srv.Width,
Height: srv.Height,
Timestamp: srv.Timestamp,
TimeFormat: srv.TimeFormat,
})
if err != nil {
stderr("%s\n", err.Error())

View File

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

View File

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

View File

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

View File

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

View File

@@ -10,18 +10,19 @@ import (
)
// NewEncoder returns a new Encoder.
func NewEncoder(w io.Writer) *Encoder {
return &Encoder{w}
func NewEncoder(w io.Writer, quality int) *Encoder {
return &Encoder{w, quality}
}
// Encoder struct.
type Encoder struct {
w io.Writer
w io.Writer
quality int
}
// Encode encodes image to JPEG.
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 {
return err
}

View File

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

View File

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

View File

@@ -24,11 +24,18 @@ func Rotate(img image.Image, angle int) image.Image {
return img
}
func Timestamp(img image.Image, format string) (image.Image, error) {
if format == "" {
format = "2006-01-02 15:04:05"
func Flip(img image.Image, dir string) image.Image {
switch dir {
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)
if !ok {
return img, fmt.Errorf("camera: %T is not a drawable image type", img)

View File

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