mirror of
https://github.com/gen2brain/cam2ip.git
synced 2025-12-16 04:18:39 +00:00
Compare commits
51 Commits
1.5
...
b302c77f20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b302c77f20 | ||
|
|
a1b32804da | ||
|
|
880b41dea2 | ||
|
|
244b4f51fc | ||
|
|
c177a0bb77 | ||
|
|
071a6c4f3c | ||
|
|
30e30117a1 | ||
|
|
fd8152f7a4 | ||
|
|
7dc02de8f4 | ||
|
|
6e046d47d0 | ||
|
|
df2f672da6 | ||
|
|
1ea7956db5 | ||
|
|
fa99c12ec1 | ||
|
|
f58b475549 | ||
|
|
d4ea63f95d | ||
|
|
7b60039e66 | ||
|
|
89298de3e9 | ||
|
|
fc22ce5871 | ||
|
|
19ea541157 | ||
|
|
be5863de1d | ||
|
|
7c3fe4be3c | ||
|
|
cc5d3ad202 | ||
|
|
cb2f566154 | ||
|
|
3b86bc0d69 | ||
|
|
8bfe9c787e | ||
|
|
12b5f29452 | ||
|
|
adf2a742e6 | ||
|
|
1c342a67df | ||
|
|
9e6d20863a | ||
|
|
3844a46486 | ||
|
|
2628bbefbf | ||
|
|
3b0b066cc1 | ||
|
|
277d6c5b48 | ||
|
|
1baf0deb39 | ||
|
|
50f28f57db | ||
|
|
b4e1af7729 | ||
|
|
1c8347fee8 | ||
|
|
63dd32115c | ||
|
|
773b8920b0 | ||
|
|
01b9c564ac | ||
|
|
f1df08acc7 | ||
|
|
5b7d51a9ac | ||
|
|
6b28dfec25 | ||
|
|
ff09bbeff4 | ||
|
|
4d73010019 | ||
|
|
13385ba650 | ||
|
|
09932a978c | ||
|
|
fb1388a10a | ||
|
|
8ebd729dcd | ||
|
|
309f6d339f | ||
|
|
3d617687fa |
22
Dockerfile
Normal file
22
Dockerfile
Normal file
@@ -0,0 +1,22 @@
|
||||
FROM golang:alpine as build
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
RUN apk add --no-cache git
|
||||
|
||||
COPY go.mod go.sum ./
|
||||
|
||||
RUN go mod download
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN CGO_ENABLED=0 go build -tags jpeg -o cam2ip -ldflags "-s -w" github.com/gen2brain/cam2ip/cmd/cam2ip
|
||||
|
||||
|
||||
FROM scratch
|
||||
|
||||
COPY --from=build /build/cam2ip /cam2ip
|
||||
|
||||
EXPOSE 56000
|
||||
|
||||
ENTRYPOINT ["/cam2ip"]
|
||||
76
README.md
76
README.md
@@ -4,73 +4,77 @@ Turn any webcam into an IP camera.
|
||||
|
||||
Example (in web browser):
|
||||
|
||||
http://localhost:56000/mjpeg
|
||||
http://localhost:56000/html
|
||||
|
||||
or
|
||||
|
||||
http://localhost:56000/html
|
||||
http://localhost:56000/mjpeg
|
||||
|
||||
You can also use apps like `ffplay` or `vlc`:
|
||||
|
||||
ffplay -i http://localhost:56000/mjpeg
|
||||
|
||||
### Requirements
|
||||
|
||||
* [libjpeg-turbo](https://www.libjpeg-turbo.org/) (use `-tags jpeg` to build without `CGo`)
|
||||
|
||||
On Linux/RPi native Go [V4L](https://github.com/korandiz/v4l) implementation is used to capture images.
|
||||
On Windows [Video for Windows (VfW)](https://en.wikipedia.org/wiki/Video_for_Windows) framework is used over win32 API.
|
||||
|
||||
### Installation
|
||||
|
||||
go get -u github.com/gen2brain/cam2ip
|
||||
* On Linux/RPi native Go [V4L](https://github.com/korandiz/v4l) implementation is used to capture images.
|
||||
* On Windows [Video for Windows (VfW)](https://en.wikipedia.org/wiki/Video_for_Windows) framework is used over win32 API.
|
||||
|
||||
### Build tags
|
||||
|
||||
* `cv2` - build with `OpenCV` 2.x ([go-opencv](https://github.com/lazywei/go-opencv))
|
||||
* `cv3` - build with `OpenCV` 3.x ([gocv](https://github.com/hybridgroup/gocv))
|
||||
* `jpeg` - build with native Go `image/jpeg` instead of `libjpeg-turbo`
|
||||
* `cv4` - build with `OpenCV` 4.x ([gocv](https://github.com/hybridgroup/gocv))
|
||||
* `turbo` - build with `libjpeg-turbo` ([libjpeg-turbo](https://www.libjpeg-turbo.org/)) instead of native Go `image/jpeg`
|
||||
|
||||
### Download
|
||||
|
||||
Binaries are compiled with static OpenCV/libjpeg-turbo libraries, they should just work:
|
||||
|
||||
- [Linux 64bit](https://github.com/gen2brain/cam2ip/releases/download/1.5/cam2ip-1.5-64bit.tar.gz)
|
||||
- [Linux 64bit OpenCV](https://github.com/gen2brain/cam2ip/releases/download/1.5/cam2ip-1.5-64bit-cv2.tar.gz)
|
||||
- [RPi 32bit](https://github.com/gen2brain/cam2ip/releases/download/1.5/cam2ip-1.5-RPi.tar.gz)
|
||||
- [RPi 32bit OpenCV](https://github.com/gen2brain/cam2ip/releases/download/1.5/cam2ip-1.5-RPi-cv2.tar.gz)
|
||||
- [RPi 32bit Static](https://github.com/gen2brain/cam2ip/releases/download/1.5/cam2ip-1.5-RPi-nocgo.tar.gz)
|
||||
- [RPi3 32bit](https://github.com/gen2brain/cam2ip/releases/download/1.5/cam2ip-1.5-RPi3.tar.gz)
|
||||
- [RPi3 32bit OpenCV](https://github.com/gen2brain/cam2ip/releases/download/1.5/cam2ip-1.5-RPi3-cv2.tar.gz)
|
||||
- [Windows 32bit](https://github.com/gen2brain/cam2ip/releases/download/1.5/cam2ip-1.5-32bit.zip)
|
||||
- [Windows 32bit OpenCV](https://github.com/gen2brain/cam2ip/releases/download/1.5/cam2ip-1.5-32bit-cv2.zip)
|
||||
- [Windows 64bit](https://github.com/gen2brain/cam2ip/releases/download/1.5/cam2ip-1.5-64bit.zip)
|
||||
- [Windows 64bit OpenCV](https://github.com/gen2brain/cam2ip/releases/download/1.5/cam2ip-1.5-64bit-cv2.zip)
|
||||
- [Linux 64bit](https://github.com/gen2brain/cam2ip/releases/download/1.6/cam2ip-1.6-64bit.tar.gz)
|
||||
- [Linux 64bit OpenCV](https://github.com/gen2brain/cam2ip/releases/download/1.6/cam2ip-1.6-64bit-cv2.tar.gz)
|
||||
- [macOS 64bit OpenCV](https://github.com/gen2brain/cam2ip/releases/download/1.6/cam2ip-1.6-darwin-cv2.zip)
|
||||
- [RPi 32bit](https://github.com/gen2brain/cam2ip/releases/download/1.6/cam2ip-1.6-RPi.tar.gz)
|
||||
- [RPi 32bit OpenCV](https://github.com/gen2brain/cam2ip/releases/download/1.6/cam2ip-1.6-RPi-cv2.tar.gz)
|
||||
- [RPi 32bit Static](https://github.com/gen2brain/cam2ip/releases/download/1.6/cam2ip-1.6-RPi-nocgo.tar.gz)
|
||||
- [RPi3 32bit](https://github.com/gen2brain/cam2ip/releases/download/1.6/cam2ip-1.6-RPi3.tar.gz)
|
||||
- [RPi3 32bit OpenCV](https://github.com/gen2brain/cam2ip/releases/download/1.6/cam2ip-1.6-RPi3-cv2.tar.gz)
|
||||
- [Windows 32bit](https://github.com/gen2brain/cam2ip/releases/download/1.6/cam2ip-1.6-32bit.zip)
|
||||
- [Windows 32bit OpenCV](https://github.com/gen2brain/cam2ip/releases/download/1.6/cam2ip-1.6-32bit-cv2.zip)
|
||||
- [Windows 64bit](https://github.com/gen2brain/cam2ip/releases/download/1.6/cam2ip-1.6-64bit.zip)
|
||||
- [Windows 64bit OpenCV](https://github.com/gen2brain/cam2ip/releases/download/1.6/cam2ip-1.6-64bit-cv2.zip)
|
||||
|
||||
|
||||
### Installation
|
||||
|
||||
go get -v github.com/gen2brain/cam2ip
|
||||
go get -v github.com/gen2brain/cam2ip/cmd/cam2ip
|
||||
|
||||
This will install app in `$GOPATH/bin/cam2ip`.
|
||||
|
||||
### Run in Docker container
|
||||
|
||||
docker run --device=/dev/video0:/dev/video0 -p56000:56000 -it gen2brain/cam2ip # on RPi use gen2brain/cam2ip:arm
|
||||
|
||||
### Usage
|
||||
|
||||
```
|
||||
Usage of ./cam2ip:
|
||||
Usage of cam2ip:
|
||||
-bind-addr string
|
||||
Bind address (default ":56000")
|
||||
Bind address [CAM2IP_BIND_ADDR] (default ":56000")
|
||||
-delay int
|
||||
Delay between frames, in milliseconds (default 10)
|
||||
Delay between frames, in milliseconds [CAM2IP_DELAY] (default 10)
|
||||
-height float
|
||||
Frame height (default 480)
|
||||
-width float
|
||||
Frame width (default 640)
|
||||
Frame height [CAM2IP_HEIGHT] (default 480)
|
||||
-htpasswd-file string
|
||||
Path to htpasswd file, if empty auth is disabled
|
||||
Path to htpasswd file, if empty auth is disabled [CAM2IP_HTPASSWD_FILE]
|
||||
-index int
|
||||
Camera index
|
||||
Camera index [CAM2IP_INDEX]
|
||||
-nowebgl
|
||||
Disable WebGL drawing of images (html handler)
|
||||
Disable WebGL drawing of images (html handler) [CAM2IP_NOWEBGL]
|
||||
-rotate int
|
||||
Rotate image, valid values are 90, 180, 270
|
||||
-video-file string
|
||||
Use video file instead of camera
|
||||
Rotate image, valid values are 90, 180, 270 [CAM2IP_ROTATE]
|
||||
-timestamp
|
||||
Draws timestamp on images [CAM2IP_TIMESTAMP]
|
||||
-width float
|
||||
Frame width [CAM2IP_WIDTH] (default 640)
|
||||
```
|
||||
|
||||
### Handlers
|
||||
|
||||
80
cam2ip_cv.go
80
cam2ip_cv.go
@@ -1,80 +0,0 @@
|
||||
// +build cv2,cv3
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/gen2brain/cam2ip/camera"
|
||||
"github.com/gen2brain/cam2ip/server"
|
||||
"github.com/gen2brain/cam2ip/video"
|
||||
)
|
||||
|
||||
const (
|
||||
name = "cam2ip"
|
||||
version = "1.5"
|
||||
)
|
||||
|
||||
func main() {
|
||||
srv := server.NewServer()
|
||||
|
||||
flag.IntVar(&srv.Index, "index", 0, "Camera index")
|
||||
flag.IntVar(&srv.Delay, "delay", 10, "Delay between frames, in milliseconds")
|
||||
flag.Float64Var(&srv.FrameWidth, "width", 640, "Frame width")
|
||||
flag.Float64Var(&srv.FrameHeight, "height", 480, "Frame height")
|
||||
flag.IntVar(&srv.Rotate, "rotate", 0, "Rotate image, valid values are 90, 180, 270")
|
||||
flag.BoolVar(&srv.NoWebGL, "nowebgl", false, "Disable WebGL drawing of images (html handler)")
|
||||
flag.StringVar(&srv.Bind, "bind-addr", ":56000", "Bind address")
|
||||
flag.StringVar(&srv.Htpasswd, "htpasswd-file", "", "Path to htpasswd file, if empty auth is disabled")
|
||||
flag.StringVar(&srv.FileName, "video-file", "", "Use video file instead of camera")
|
||||
flag.Parse()
|
||||
|
||||
srv.Name = name
|
||||
srv.Version = version
|
||||
|
||||
var err error
|
||||
|
||||
if srv.Htpasswd != "" {
|
||||
if _, err = os.Stat(srv.Htpasswd); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
if srv.FileName != "" {
|
||||
if _, err = os.Stat(srv.FileName); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
if srv.FileName != "" {
|
||||
vid, err := video.New(video.Options{srv.FileName, srv.Rotate})
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
srv.Reader = vid
|
||||
} else {
|
||||
cam, err := camera.New(camera.Options{srv.Index, srv.Rotate, srv.FrameWidth, srv.FrameHeight})
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
srv.Reader = cam
|
||||
}
|
||||
|
||||
defer srv.Reader.Close()
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Listening on %s\n", srv.Bind)
|
||||
|
||||
err = srv.ListenAndServe()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
10
camera/camera.go
Normal file
10
camera/camera.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package camera
|
||||
|
||||
// Options.
|
||||
type Options struct {
|
||||
Index int
|
||||
Rotate int
|
||||
Width float64
|
||||
Height float64
|
||||
Timestamp bool
|
||||
}
|
||||
304
camera/camera_android.go
Normal file
304
camera/camera_android.go
Normal file
@@ -0,0 +1,304 @@
|
||||
//go:build android
|
||||
// +build android
|
||||
|
||||
// Package camera.
|
||||
package camera
|
||||
|
||||
/*
|
||||
#include <android/log.h>
|
||||
|
||||
#include <media/NdkImageReader.h>
|
||||
|
||||
#include <camera/NdkCameraDevice.h>
|
||||
#include <camera/NdkCameraManager.h>
|
||||
|
||||
#define TAG "camera"
|
||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
|
||||
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, TAG, __VA_ARGS__)
|
||||
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
|
||||
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
|
||||
|
||||
AImage *image;
|
||||
AImageReader *imageReader;
|
||||
|
||||
ANativeWindow *nativeWindow;
|
||||
|
||||
ACameraDevice *cameraDevice;
|
||||
ACameraManager *cameraManager;
|
||||
ACameraOutputTarget *cameraOutputTarget;
|
||||
ACameraCaptureSession *cameraCaptureSession;
|
||||
|
||||
ACaptureRequest *captureRequest;
|
||||
ACaptureSessionOutput *captureSessionOutput;
|
||||
ACaptureSessionOutputContainer *captureSessionOutputContainer;
|
||||
|
||||
void device_on_disconnected(void *context, ACameraDevice *device) {
|
||||
LOGI("camera %s is diconnected.\n", ACameraDevice_getId(device));
|
||||
}
|
||||
|
||||
void device_on_error(void *context, ACameraDevice *device, int error) {
|
||||
LOGE("error %d on camera %s.\n", error, ACameraDevice_getId(device));
|
||||
}
|
||||
|
||||
ACameraDevice_stateCallbacks deviceStateCallbacks = {
|
||||
.context = NULL,
|
||||
.onDisconnected = device_on_disconnected,
|
||||
.onError = device_on_error,
|
||||
};
|
||||
|
||||
void session_on_ready(void *context, ACameraCaptureSession *session) {
|
||||
LOGI("session is ready. %p\n", session);
|
||||
}
|
||||
|
||||
void session_on_active(void *context, ACameraCaptureSession *session) {
|
||||
LOGI("session is activated. %p\n", session);
|
||||
}
|
||||
|
||||
void session_on_closed(void *context, ACameraCaptureSession *session) {
|
||||
LOGI("session is closed. %p\n", session);
|
||||
}
|
||||
|
||||
ACameraCaptureSession_stateCallbacks captureSessionStateCallbacks = {
|
||||
.context = NULL,
|
||||
.onActive = session_on_active,
|
||||
.onReady = session_on_ready,
|
||||
.onClosed = session_on_closed,
|
||||
};
|
||||
|
||||
void image_callback(void *context, AImageReader *reader) {
|
||||
LOGD("image_callback");
|
||||
|
||||
media_status_t status = AImageReader_acquireLatestImage(reader, &image);
|
||||
if(status != AMEDIA_OK) {
|
||||
LOGE("failed to acquire next image (reason: %d).\n", status);
|
||||
}
|
||||
}
|
||||
|
||||
AImageReader_ImageListener imageListener = {
|
||||
.context = NULL,
|
||||
.onImageAvailable = image_callback,
|
||||
};
|
||||
|
||||
int openCamera(int index, int width, int height) {
|
||||
ACameraIdList *cameraIdList;
|
||||
const char *selectedCameraId;
|
||||
|
||||
camera_status_t status = ACAMERA_OK;
|
||||
|
||||
cameraManager = ACameraManager_create();
|
||||
|
||||
status = ACameraManager_getCameraIdList(cameraManager, &cameraIdList);
|
||||
if(status != ACAMERA_OK) {
|
||||
LOGE("failed to get camera id list (reason: %d).\n", status);
|
||||
return status;
|
||||
}
|
||||
|
||||
if(cameraIdList->numCameras < 1) {
|
||||
LOGE("no camera device detected.\n");
|
||||
}
|
||||
|
||||
if(cameraIdList->numCameras < index+1) {
|
||||
LOGE("no camera at index %d.\n", index);
|
||||
}
|
||||
|
||||
selectedCameraId = cameraIdList->cameraIds[index];
|
||||
LOGI("open camera (id: %s, num of cameras: %d).\n", selectedCameraId, cameraIdList->numCameras);
|
||||
|
||||
status = ACameraManager_openCamera(cameraManager, selectedCameraId, &deviceStateCallbacks, &cameraDevice);
|
||||
if(status != ACAMERA_OK) {
|
||||
LOGE("failed to open camera device (id: %s)\n", selectedCameraId);
|
||||
return status;
|
||||
}
|
||||
|
||||
status = ACameraDevice_createCaptureRequest(cameraDevice, TEMPLATE_STILL_CAPTURE, &captureRequest);
|
||||
if(status != ACAMERA_OK) {
|
||||
LOGE("failed to create snapshot capture request (id: %s)\n", selectedCameraId);
|
||||
return status;
|
||||
}
|
||||
|
||||
status = ACaptureSessionOutputContainer_create(&captureSessionOutputContainer);
|
||||
if(status != ACAMERA_OK) {
|
||||
LOGE("failed to create session output container (id: %s)\n", selectedCameraId);
|
||||
return status;
|
||||
}
|
||||
|
||||
media_status_t mstatus = AImageReader_new(width, height, AIMAGE_FORMAT_YUV_420_888, 2, &imageReader);
|
||||
if(mstatus != AMEDIA_OK) {
|
||||
LOGE("failed to create image reader (reason: %d).\n", mstatus);
|
||||
return mstatus;
|
||||
}
|
||||
|
||||
mstatus = AImageReader_setImageListener(imageReader, &imageListener);
|
||||
if(mstatus != AMEDIA_OK) {
|
||||
LOGE("failed to set image listener (reason: %d).\n", mstatus);
|
||||
return mstatus;
|
||||
}
|
||||
|
||||
AImageReader_getWindow(imageReader, &nativeWindow);
|
||||
ANativeWindow_acquire(nativeWindow);
|
||||
|
||||
ACameraOutputTarget_create(nativeWindow, &cameraOutputTarget);
|
||||
ACaptureRequest_addTarget(captureRequest, cameraOutputTarget);
|
||||
|
||||
ACaptureSessionOutput_create(nativeWindow, &captureSessionOutput);
|
||||
ACaptureSessionOutputContainer_add(captureSessionOutputContainer, captureSessionOutput);
|
||||
|
||||
status = ACameraDevice_createCaptureSession(cameraDevice, captureSessionOutputContainer, &captureSessionStateCallbacks, &cameraCaptureSession);
|
||||
if(status != ACAMERA_OK) {
|
||||
LOGE("failed to create capture session (reason: %d).\n", status);
|
||||
return status;
|
||||
}
|
||||
|
||||
ACameraManager_deleteCameraIdList(cameraIdList);
|
||||
ACameraManager_delete(cameraManager);
|
||||
|
||||
return ACAMERA_OK;
|
||||
}
|
||||
|
||||
int captureCamera() {
|
||||
camera_status_t status = ACameraCaptureSession_capture(cameraCaptureSession, NULL, 1, &captureRequest, NULL);
|
||||
if(status != ACAMERA_OK) {
|
||||
LOGE("failed to capture image (reason: %d).\n", status);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
int closeCamera() {
|
||||
camera_status_t status = ACAMERA_OK;
|
||||
|
||||
if(captureRequest != NULL) {
|
||||
ACaptureRequest_free(captureRequest);
|
||||
captureRequest = NULL;
|
||||
}
|
||||
|
||||
if(cameraOutputTarget != NULL) {
|
||||
ACameraOutputTarget_free(cameraOutputTarget);
|
||||
cameraOutputTarget = NULL;
|
||||
}
|
||||
|
||||
if(cameraDevice != NULL) {
|
||||
status = ACameraDevice_close(cameraDevice);
|
||||
|
||||
if(status != ACAMERA_OK) {
|
||||
LOGE("failed to close camera device.\n");
|
||||
return status;
|
||||
}
|
||||
|
||||
cameraDevice = NULL;
|
||||
}
|
||||
|
||||
if(captureSessionOutput != NULL) {
|
||||
ACaptureSessionOutput_free(captureSessionOutput);
|
||||
captureSessionOutput = NULL;
|
||||
}
|
||||
|
||||
if(captureSessionOutputContainer != NULL) {
|
||||
ACaptureSessionOutputContainer_free(captureSessionOutputContainer);
|
||||
captureSessionOutputContainer = NULL;
|
||||
}
|
||||
|
||||
if(imageReader != NULL) {
|
||||
AImageReader_delete(imageReader);
|
||||
imageReader = NULL;
|
||||
}
|
||||
|
||||
if(image != NULL) {
|
||||
AImage_delete(image);
|
||||
image = NULL;
|
||||
}
|
||||
|
||||
LOGI("camera closed.\n");
|
||||
return ACAMERA_OK;
|
||||
}
|
||||
|
||||
int openCamera(int index, int width, int height);
|
||||
int captureCamera();
|
||||
int closeCamera();
|
||||
|
||||
#cgo android CFLAGS: -D__ANDROID_API__=24
|
||||
#cgo android LDFLAGS: -lcamera2ndk -lmediandk -llog -landroid
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Camera represents camera.
|
||||
type Camera struct {
|
||||
opts Options
|
||||
img *image.YCbCr
|
||||
}
|
||||
|
||||
// New returns new Camera for given camera index.
|
||||
func New(opts Options) (camera *Camera, err error) {
|
||||
camera = &Camera{}
|
||||
camera.opts = opts
|
||||
|
||||
camera.img = image.NewYCbCr(image.Rect(0, 0, int(opts.Width), int(opts.Height)), image.YCbCrSubsampleRatio420)
|
||||
|
||||
ret := C.openCamera(C.int(opts.Index), C.int(opts.Width), C.int(opts.Height))
|
||||
if bool(int(ret) != 0) {
|
||||
err = fmt.Errorf("camera: can not open camera %d: error %d", opts.Index, int(ret))
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Read reads next frame from camera and returns image.
|
||||
func (c *Camera) Read() (img image.Image, err error) {
|
||||
ret := C.captureCamera()
|
||||
if bool(int(ret) != 0) {
|
||||
err = fmt.Errorf("camera: can not grab frame: error %d", int(ret))
|
||||
return
|
||||
}
|
||||
|
||||
if C.image == nil {
|
||||
err = fmt.Errorf("camera: can not retrieve frame")
|
||||
return
|
||||
}
|
||||
|
||||
var yStride C.int
|
||||
var yLen, cbLen, crLen C.int
|
||||
var yPtr, cbPtr, crPtr *C.uint8_t
|
||||
|
||||
C.AImage_getPlaneRowStride(C.image, 0, &yStride)
|
||||
C.AImage_getPlaneData(C.image, 0, &yPtr, &yLen)
|
||||
C.AImage_getPlaneData(C.image, 1, &cbPtr, &cbLen)
|
||||
C.AImage_getPlaneData(C.image, 2, &crPtr, &crLen)
|
||||
|
||||
c.img.YStride = int(yStride)
|
||||
c.img.CStride = int(yStride) / 2
|
||||
|
||||
c.img.Y = C.GoBytes(unsafe.Pointer(yPtr), yLen)
|
||||
c.img.Cb = C.GoBytes(unsafe.Pointer(cbPtr), cbLen)
|
||||
c.img.Cr = C.GoBytes(unsafe.Pointer(crPtr), crLen)
|
||||
|
||||
img = c.img
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 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) {
|
||||
}
|
||||
|
||||
// Close closes camera.
|
||||
func (c *Camera) Close() (err error) {
|
||||
ret := C.closeCamera()
|
||||
if bool(int(ret) != 0) {
|
||||
err = fmt.Errorf("camera: can not close camera %d: error %d", c.opts.Index, int(ret))
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
// +build cv2 cv3
|
||||
//go:build cv2 || cv4
|
||||
// +build cv2 cv4
|
||||
|
||||
package camera
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// +build !cv2,!cv3
|
||||
//go:build !cv2 && !cv4 && !android
|
||||
// +build !cv2,!cv4,!android
|
||||
|
||||
package camera
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// +build cv2,!cv3
|
||||
//go:build cv2 && !cv4
|
||||
// +build cv2,!cv4
|
||||
|
||||
// Package camera.
|
||||
package camera
|
||||
@@ -6,19 +7,15 @@ package camera
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
"time"
|
||||
|
||||
"github.com/disintegration/imaging"
|
||||
"github.com/lazywei/go-opencv/opencv"
|
||||
"github.com/gen2brain/go-opencv/opencv"
|
||||
"github.com/pbnjay/pixfont"
|
||||
)
|
||||
|
||||
// Options.
|
||||
type Options struct {
|
||||
Index int
|
||||
Rotate int
|
||||
Width float64
|
||||
Height float64
|
||||
}
|
||||
|
||||
// Camera represents camera.
|
||||
type Camera struct {
|
||||
opts Options
|
||||
@@ -44,29 +41,37 @@ func New(opts Options) (camera *Camera, err error) {
|
||||
|
||||
// Read reads next frame from camera and returns image.
|
||||
func (c *Camera) Read() (img image.Image, err error) {
|
||||
if c.camera.GrabFrame() {
|
||||
c.frame = c.camera.RetrieveFrame(1)
|
||||
|
||||
if c.frame == nil {
|
||||
err = fmt.Errorf("camera: can not retrieve frame")
|
||||
return
|
||||
}
|
||||
|
||||
img = c.frame.ToImage()
|
||||
if c.opts.Rotate == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
switch c.opts.Rotate {
|
||||
case 90:
|
||||
img = imaging.Rotate90(img)
|
||||
case 180:
|
||||
img = imaging.Rotate180(img)
|
||||
case 270:
|
||||
img = imaging.Rotate270(img)
|
||||
}
|
||||
} else {
|
||||
if !c.camera.GrabFrame() {
|
||||
err = fmt.Errorf("camera: can not grab frame")
|
||||
return
|
||||
}
|
||||
|
||||
c.frame = c.camera.RetrieveFrame(1)
|
||||
if c.frame == nil {
|
||||
err = fmt.Errorf("camera: can not retrieve frame")
|
||||
return
|
||||
}
|
||||
|
||||
img = c.frame.ToImage()
|
||||
|
||||
switch c.opts.Rotate {
|
||||
case 90:
|
||||
img = imaging.Rotate90(img)
|
||||
case 180:
|
||||
img = imaging.Rotate180(img)
|
||||
case 270:
|
||||
img = imaging.Rotate270(img)
|
||||
}
|
||||
|
||||
if c.opts.Timestamp {
|
||||
dimg, ok := img.(draw.Image)
|
||||
if !ok {
|
||||
err = fmt.Errorf("camera: %T is not a drawable image type", img)
|
||||
return
|
||||
}
|
||||
|
||||
pixfont.DrawString(dimg, 10, 10, time.Now().Format("2006-01-02 15:04:05"), color.White)
|
||||
img = dimg
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// +build cv3,!cv2
|
||||
//go:build cv4 && !cv2
|
||||
// +build cv4,!cv2
|
||||
|
||||
// Package camera.
|
||||
package camera
|
||||
@@ -6,19 +7,15 @@ package camera
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
"time"
|
||||
|
||||
"github.com/disintegration/imaging"
|
||||
"github.com/pbnjay/pixfont"
|
||||
"gocv.io/x/gocv"
|
||||
)
|
||||
|
||||
// Options.
|
||||
type Options struct {
|
||||
Index int
|
||||
Rotate int
|
||||
Width float64
|
||||
Height float64
|
||||
}
|
||||
|
||||
// Camera represents camera.
|
||||
type Camera struct {
|
||||
opts Options
|
||||
@@ -64,10 +61,6 @@ func (c *Camera) Read() (img image.Image, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
if c.opts.Rotate == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
switch c.opts.Rotate {
|
||||
case 90:
|
||||
img = imaging.Rotate90(img)
|
||||
@@ -77,6 +70,17 @@ func (c *Camera) Read() (img image.Image, err error) {
|
||||
img = imaging.Rotate270(img)
|
||||
}
|
||||
|
||||
if c.opts.Timestamp {
|
||||
dimg, ok := img.(draw.Image)
|
||||
if !ok {
|
||||
err = fmt.Errorf("camera: %T is not a drawable image type", img)
|
||||
return
|
||||
}
|
||||
|
||||
pixfont.DrawString(dimg, 10, 10, time.Now().Format("2006-01-02 15:04:05"), color.White)
|
||||
img = dimg
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// +build !cv2,!cv3
|
||||
//go:build !cv2 && !cv4 && !android
|
||||
// +build !cv2,!cv4,!android
|
||||
|
||||
// Package camera.
|
||||
package camera
|
||||
@@ -6,22 +7,18 @@ package camera
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
"time"
|
||||
|
||||
"github.com/disintegration/imaging"
|
||||
"github.com/korandiz/v4l"
|
||||
"github.com/korandiz/v4l/fmt/mjpeg"
|
||||
"github.com/pbnjay/pixfont"
|
||||
|
||||
im "github.com/gen2brain/cam2ip/image"
|
||||
)
|
||||
|
||||
// Options.
|
||||
type Options struct {
|
||||
Index int
|
||||
Rotate int
|
||||
Width float64
|
||||
Height float64
|
||||
}
|
||||
|
||||
// Camera represents camera.
|
||||
type Camera struct {
|
||||
opts Options
|
||||
@@ -90,10 +87,6 @@ func (c *Camera) Read() (img image.Image, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
if c.opts.Rotate == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
switch c.opts.Rotate {
|
||||
case 90:
|
||||
img = imaging.Rotate90(img)
|
||||
@@ -103,6 +96,17 @@ func (c *Camera) Read() (img image.Image, err error) {
|
||||
img = imaging.Rotate270(img)
|
||||
}
|
||||
|
||||
if c.opts.Timestamp {
|
||||
dimg, ok := img.(draw.Image)
|
||||
if !ok {
|
||||
err = fmt.Errorf("camera: %T is not a drawable image type", img)
|
||||
return
|
||||
}
|
||||
|
||||
pixfont.DrawString(dimg, 10, 10, time.Now().Format("2006-01-02 15:04:05"), color.White)
|
||||
img = dimg
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// +build !cv2,!cv3
|
||||
//go:build !cv2 && !cv4
|
||||
// +build !cv2,!cv4
|
||||
|
||||
// Package camera.
|
||||
package camera
|
||||
@@ -7,20 +8,16 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/disintegration/imaging"
|
||||
"github.com/pbnjay/pixfont"
|
||||
)
|
||||
|
||||
// Options.
|
||||
type Options struct {
|
||||
Index int
|
||||
Rotate int
|
||||
Width float64
|
||||
Height float64
|
||||
}
|
||||
|
||||
// Camera represents camera.
|
||||
type Camera struct {
|
||||
opts Options
|
||||
@@ -137,9 +134,6 @@ func (c *Camera) Read() (img image.Image, err error) {
|
||||
}
|
||||
|
||||
img = c.frame
|
||||
if c.opts.Rotate == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
switch c.opts.Rotate {
|
||||
case 90:
|
||||
@@ -150,6 +144,17 @@ func (c *Camera) Read() (img image.Image, err error) {
|
||||
img = imaging.Rotate270(img)
|
||||
}
|
||||
|
||||
if c.opts.Timestamp {
|
||||
dimg, ok := img.(draw.Image)
|
||||
if !ok {
|
||||
err = fmt.Errorf("camera: %T is not a drawable image type", img)
|
||||
return
|
||||
}
|
||||
|
||||
pixfont.DrawString(dimg, 10, 10, time.Now().Format("2006-01-02 15:04:05"), color.White)
|
||||
img = dimg
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -5,13 +5,15 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/jamiealquiza/envy"
|
||||
|
||||
"github.com/gen2brain/cam2ip/camera"
|
||||
"github.com/gen2brain/cam2ip/server"
|
||||
)
|
||||
|
||||
const (
|
||||
name = "cam2ip"
|
||||
version = "1.5"
|
||||
version = "1.6"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -23,8 +25,11 @@ func main() {
|
||||
flag.Float64Var(&srv.FrameHeight, "height", 480, "Frame height")
|
||||
flag.IntVar(&srv.Rotate, "rotate", 0, "Rotate image, valid values are 90, 180, 270")
|
||||
flag.BoolVar(&srv.NoWebGL, "nowebgl", false, "Disable WebGL drawing of images (html handler)")
|
||||
flag.BoolVar(&srv.Timestamp, "timestamp", false, "Draws timestamp on images")
|
||||
flag.StringVar(&srv.Bind, "bind-addr", ":56000", "Bind address")
|
||||
flag.StringVar(&srv.Htpasswd, "htpasswd-file", "", "Path to htpasswd file, if empty auth is disabled")
|
||||
|
||||
envy.Parse("CAM2IP")
|
||||
flag.Parse()
|
||||
|
||||
srv.Name = name
|
||||
@@ -46,7 +51,7 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
cam, err := camera.New(camera.Options{srv.Index, srv.Rotate, srv.FrameWidth, srv.FrameHeight})
|
||||
cam, err := camera.New(camera.Options{srv.Index, srv.Rotate, srv.FrameWidth, srv.FrameHeight, srv.Timestamp})
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
|
||||
os.Exit(1)
|
||||
26
go.mod
Normal file
26
go.mod
Normal file
@@ -0,0 +1,26 @@
|
||||
module github.com/gen2brain/cam2ip
|
||||
|
||||
require (
|
||||
github.com/abbot/go-http-auth v0.4.0
|
||||
github.com/antonini/golibjpegturbo v0.0.0-20141208033414-c03a2fa1e89a
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/gen2brain/base64 v0.0.0-20221015184129-317a5c93030c
|
||||
github.com/gen2brain/go-opencv v0.0.0-20191005190506-bf186fc94f7a
|
||||
github.com/jamiealquiza/envy v1.1.0
|
||||
github.com/korandiz/v4l v1.1.0
|
||||
github.com/pbnjay/pixfont v0.0.0-20200714042608-33b744692567
|
||||
gocv.io/x/gocv v0.35.0
|
||||
nhooyr.io/websocket v1.8.10
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/spf13/cobra v1.8.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
golang.org/x/crypto v0.18.0 // indirect
|
||||
golang.org/x/image v0.15.0 // indirect
|
||||
golang.org/x/net v0.20.0 // indirect
|
||||
golang.org/x/sys v0.16.0 // indirect
|
||||
)
|
||||
|
||||
go 1.21
|
||||
42
go.sum
Normal file
42
go.sum
Normal file
@@ -0,0 +1,42 @@
|
||||
github.com/abbot/go-http-auth v0.4.0 h1:QjmvZ5gSC7jm3Zg54DqWE/T5m1t2AfDu6QlXJT0EVT0=
|
||||
github.com/abbot/go-http-auth v0.4.0/go.mod h1:Cz6ARTIzApMJDzh5bRMSUou6UMSp0IEXg9km/ci7TJM=
|
||||
github.com/antonini/golibjpegturbo v0.0.0-20141208033414-c03a2fa1e89a h1:+Q4qlzO9KeavJSvWgRKckT3ViTSQ8rR6GroSiXPRXhs=
|
||||
github.com/antonini/golibjpegturbo v0.0.0-20141208033414-c03a2fa1e89a/go.mod h1:UOX4aiVZ5WVUBY3D/31H4m6Z8UHgBj5Qr/oldRBBUMY=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||
github.com/gen2brain/base64 v0.0.0-20221015184129-317a5c93030c h1:TUjjeJ2rV4KZxH6hIEi/boEQB3v6aKvwdakUJR3AwiE=
|
||||
github.com/gen2brain/base64 v0.0.0-20221015184129-317a5c93030c/go.mod h1:VG58IUyxPWojCtGwqwoZ/6LLXwClu1tssqa5ktOxI9o=
|
||||
github.com/gen2brain/go-opencv v0.0.0-20191005190506-bf186fc94f7a h1:0arrt5Ke40opD5glNdh9ltrkZ0jaqPWsquGPSE3ukug=
|
||||
github.com/gen2brain/go-opencv v0.0.0-20191005190506-bf186fc94f7a/go.mod h1:pOLh42huXUuMoJWvD2K+EeXzvQ9GZ5HN6gdFk5ZwIuU=
|
||||
github.com/hybridgroup/mjpeg v0.0.0-20140228234708-4680f319790e/go.mod h1:eagM805MRKrioHYuU7iKLUyFPVKqVV6um5DAvCkUtXs=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jamiealquiza/envy v1.1.0 h1:Nwh4wqTZ28gDA8zB+wFkhnUpz3CEcO12zotjeqqRoKE=
|
||||
github.com/jamiealquiza/envy v1.1.0/go.mod h1:MP36BriGCLwEHhi1OU8E9569JNZrjWfCvzG7RsPnHus=
|
||||
github.com/korandiz/v4l v1.1.0 h1:VbzaWlhqNzVPfHEYEM+V8T7184ndiEzljJgDHSHc7pc=
|
||||
github.com/korandiz/v4l v1.1.0/go.mod h1:pftxPG7hkuUgepioAY6PAE81mShaVjzd95X/WF4Izus=
|
||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pbnjay/pixfont v0.0.0-20200714042608-33b744692567 h1:pKjmNHL7BCXhgsnSlN6Ov3WAN2jbJMCx6IvrMN9GNfc=
|
||||
github.com/pbnjay/pixfont v0.0.0-20200714042608-33b744692567/go.mod h1:ytYavTmrpWG4s7UOfDhP6m4ASL5XA66nrOcUn1e2M78=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
||||
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
gocv.io/x/gocv v0.35.0 h1:Qaxb5KdVyy8Spl4S4K0SMZ6CVmKtbfoSGQAxRD3FZlw=
|
||||
gocv.io/x/gocv v0.35.0/go.mod h1:oc6FvfYqfBp99p+yOEzs9tbYF9gOrAQSeL/dyIPefJU=
|
||||
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
|
||||
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
||||
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
|
||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
nhooyr.io/websocket v1.8.10 h1:mv4p+MnGrLDcPlBoWsvPP7XCzTYMXP9F9eIGoKbgx7Q=
|
||||
nhooyr.io/websocket v1.8.10/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c=
|
||||
@@ -13,20 +13,13 @@ type HTML struct {
|
||||
}
|
||||
|
||||
// NewHTML returns new HTML handler.
|
||||
func NewHTML(bind string, width, height float64, nogl bool) *HTML {
|
||||
func NewHTML(width, height float64, nogl bool) *HTML {
|
||||
h := &HTML{}
|
||||
|
||||
b := strings.Split(bind, ":")
|
||||
if b[0] == "" {
|
||||
bind = "127.0.0.1" + bind
|
||||
}
|
||||
|
||||
tpl := htmlWebGL
|
||||
if nogl {
|
||||
tpl = html
|
||||
}
|
||||
|
||||
tpl = strings.Replace(tpl, "{BIND}", bind, -1)
|
||||
tpl = strings.Replace(tpl, "{WIDTH}", fmt.Sprintf("%.0f", width), -1)
|
||||
tpl = strings.Replace(tpl, "{HEIGHT}", fmt.Sprintf("%.0f", height), -1)
|
||||
|
||||
@@ -52,7 +45,7 @@ var html = `<html>
|
||||
<meta charset="utf-8"/>
|
||||
<title>cam2ip</title>
|
||||
<script>
|
||||
ws = new WebSocket("ws://{BIND}/socket");
|
||||
ws = new WebSocket("ws://" + window.location.host + "/socket");
|
||||
var image = new Image();
|
||||
|
||||
ws.onopen = function() {
|
||||
@@ -85,11 +78,11 @@ var htmlWebGL = `<html>
|
||||
<script>
|
||||
var texture, vloc, tloc, vertexBuff, textureBuff;
|
||||
|
||||
ws = new WebSocket("ws://{BIND}/socket");
|
||||
ws = new WebSocket("ws://" + window.location.host + "/socket");
|
||||
var image = new Image();
|
||||
|
||||
ws.onopen = function() {
|
||||
var gl = document.getElementById('canvas').getContext('webgl');
|
||||
var gl = document.getElementById('canvas').getContext('webgl',{antialias:false}) || canvas.getContext('experimental-webgl');
|
||||
|
||||
var vertexShaderSrc =
|
||||
"attribute vec2 aVertex;" +
|
||||
|
||||
32
handlers/index.go
Normal file
32
handlers/index.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Index handler.
|
||||
type Index struct {
|
||||
}
|
||||
|
||||
// NewIndex returns new Index handler.
|
||||
func NewIndex() *Index {
|
||||
return &Index{}
|
||||
}
|
||||
|
||||
// ServeHTTP handles requests on incoming connections.
|
||||
func (i *Index) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "GET" && r.Method != "HEAD" {
|
||||
http.Error(w, "405 Method Not Allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
w.Write([]byte(`<html>
|
||||
<head><title>cam2ip</title></head>
|
||||
<body>
|
||||
<h1>cam2ip</h1>
|
||||
<p><a href='/html'>html</a></p>
|
||||
<p><a href='/jpeg'>jpeg</a></p>
|
||||
<p><a href='/mjpeg'>mjpeg</a></p>
|
||||
</body>
|
||||
</html>`))
|
||||
}
|
||||
@@ -20,7 +20,7 @@ func NewJPEG(reader reader.ImageReader) *JPEG {
|
||||
|
||||
// ServeHTTP handles requests on incoming connections.
|
||||
func (j *JPEG) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "GET" {
|
||||
if r.Method != "GET" && r.Method != "HEAD" {
|
||||
http.Error(w, "405 Method Not Allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ func NewMJPEG(reader reader.ImageReader, delay int) *MJPEG {
|
||||
|
||||
// ServeHTTP handles requests on incoming connections.
|
||||
func (m *MJPEG) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "GET" {
|
||||
if r.Method != "GET" && r.Method != "HEAD" {
|
||||
http.Error(w, "405 Method Not Allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
// +build !amd64
|
||||
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"context"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/websocket"
|
||||
"nhooyr.io/websocket"
|
||||
|
||||
"github.com/gen2brain/cam2ip/image"
|
||||
"github.com/gen2brain/cam2ip/reader"
|
||||
@@ -21,13 +20,20 @@ type Socket struct {
|
||||
}
|
||||
|
||||
// NewSocket returns new socket handler.
|
||||
func NewSocket(reader reader.ImageReader, delay int) websocket.Handler {
|
||||
s := &Socket{reader, delay}
|
||||
return websocket.Handler(s.write)
|
||||
func NewSocket(reader reader.ImageReader, delay int) *Socket {
|
||||
return &Socket{reader, delay}
|
||||
}
|
||||
|
||||
// write writes images to socket
|
||||
func (s *Socket) write(ws *websocket.Conn) {
|
||||
// ServeHTTP handles requests on incoming connections.
|
||||
func (s *Socket) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
conn, err := websocket.Accept(w, r, nil)
|
||||
if err != nil {
|
||||
log.Printf("socket: accept: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
for {
|
||||
img, err := s.reader.Read()
|
||||
if err != nil {
|
||||
@@ -43,13 +49,15 @@ func (s *Socket) write(ws *websocket.Conn) {
|
||||
continue
|
||||
}
|
||||
|
||||
b64 := base64.StdEncoding.EncodeToString(w.Bytes())
|
||||
b64 := image.EncodeToString(w.Bytes())
|
||||
|
||||
_, err = ws.Write([]byte(b64))
|
||||
err = conn.Write(ctx, websocket.MessageText, []byte(b64))
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
time.Sleep(time.Duration(s.delay) * time.Millisecond)
|
||||
}
|
||||
|
||||
conn.Close(websocket.StatusNormalClosure, "")
|
||||
}
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/websocket"
|
||||
"goost.org/encoding/base64"
|
||||
|
||||
"github.com/gen2brain/cam2ip/image"
|
||||
"github.com/gen2brain/cam2ip/reader"
|
||||
)
|
||||
|
||||
// Socket handler.
|
||||
type Socket struct {
|
||||
reader reader.ImageReader
|
||||
delay int
|
||||
}
|
||||
|
||||
// NewSocket returns new socket handler.
|
||||
func NewSocket(reader reader.ImageReader, delay int) websocket.Handler {
|
||||
s := &Socket{reader, delay}
|
||||
return websocket.Handler(s.write)
|
||||
}
|
||||
|
||||
// write writes images to socket
|
||||
func (s *Socket) write(ws *websocket.Conn) {
|
||||
for {
|
||||
img, err := s.reader.Read()
|
||||
if err != nil {
|
||||
log.Printf("socket: read: %v", err)
|
||||
break
|
||||
}
|
||||
|
||||
w := new(bytes.Buffer)
|
||||
|
||||
err = image.NewEncoder(w).Encode(img)
|
||||
if err != nil {
|
||||
log.Printf("socket: encode: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
b64 := base64.StdEncoding.EncodeToString(w.Bytes())
|
||||
|
||||
_, err = ws.Write([]byte(b64))
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
time.Sleep(time.Duration(s.delay) * time.Millisecond)
|
||||
}
|
||||
}
|
||||
12
image/base64.go
Normal file
12
image/base64.go
Normal file
@@ -0,0 +1,12 @@
|
||||
//go:build !amd64
|
||||
// +build !amd64
|
||||
|
||||
package image
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
)
|
||||
|
||||
func EncodeToString(src []byte) string {
|
||||
return base64.StdEncoding.EncodeToString(src)
|
||||
}
|
||||
12
image/base64_amd64.go
Normal file
12
image/base64_amd64.go
Normal file
@@ -0,0 +1,12 @@
|
||||
//go:build amd64
|
||||
// +build amd64
|
||||
|
||||
package image
|
||||
|
||||
import (
|
||||
"github.com/gen2brain/base64"
|
||||
)
|
||||
|
||||
func EncodeToString(src []byte) string {
|
||||
return base64.StdEncoding.EncodeToString(src)
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
// +build !jpeg
|
||||
//go:build turbo
|
||||
// +build turbo
|
||||
|
||||
// Package image.
|
||||
package image
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// +build jpeg
|
||||
//go:build !turbo
|
||||
// +build !turbo
|
||||
|
||||
// Package image.
|
||||
package image
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// +build !jpeg
|
||||
//go:build turbo
|
||||
// +build turbo
|
||||
|
||||
// Package image.
|
||||
package image
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// +build jpeg
|
||||
//go:build !turbo
|
||||
// +build !turbo
|
||||
|
||||
// Package image.
|
||||
package image
|
||||
|
||||
84
make.bash
84
make.bash
@@ -5,38 +5,24 @@ MINGW="/usr/i686-w64-mingw32"
|
||||
MINGW64="/usr/x86_64-w64-mingw32"
|
||||
RPI="/usr/armv6j-hardfloat-linux-gnueabi"
|
||||
RPI3="/usr/armv7a-hardfloat-linux-gnueabi"
|
||||
APPLE="/usr/x86_64-apple-darwin14"
|
||||
ANDROID="/usr/arm-linux-androideabi"
|
||||
|
||||
mkdir -p build
|
||||
|
||||
LIBRARY_PATH="$CHROOT/usr/lib:$CHROOT/lib" \
|
||||
PKG_CONFIG_PATH="$CHROOT/usr/lib/pkgconfig" \
|
||||
PKG_CONFIG_LIBDIR="$CHROOT/usr/lib/pkgconfig" \
|
||||
CGO_LDFLAGS="-L$CHROOT/usr/lib -L$CHROOT/lib" \
|
||||
LIBRARY_PATH="$CHROOT/usr/lib64:$CHROOT/lib64" \
|
||||
PKG_CONFIG_PATH="$CHROOT/usr/lib64/pkgconfig" \
|
||||
PKG_CONFIG_LIBDIR="$CHROOT/usr/lib64/pkgconfig" \
|
||||
CGO_LDFLAGS="-L$CHROOT/usr/lib64 -L$CHROOT/lib" \
|
||||
CGO_CFLAGS="-I$CHROOT/usr/include" \
|
||||
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -tags cv2 -o build/cam2ip.linux.amd64.cv2 -ldflags "-linkmode external -s -w" github.com/gen2brain/cam2ip
|
||||
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -tags cv2,turbo -o build/cam2ip.linux.amd64.cv2 -ldflags "-linkmode external -s -w" github.com/gen2brain/cam2ip/cmd/cam2ip
|
||||
|
||||
LIBRARY_PATH="$CHROOT/usr/lib:$CHROOT/lib" \
|
||||
PKG_CONFIG_PATH="$CHROOT/usr/lib/pkgconfig" \
|
||||
PKG_CONFIG_LIBDIR="$CHROOT/usr/lib/pkgconfig" \
|
||||
CGO_LDFLAGS="-L$CHROOT/usr/lib -L$CHROOT/lib" \
|
||||
LIBRARY_PATH="$CHROOT/usr/lib64:$CHROOT/lib64" \
|
||||
PKG_CONFIG_PATH="$CHROOT/usr/lib64/pkgconfig" \
|
||||
PKG_CONFIG_LIBDIR="$CHROOT/usr/lib64/pkgconfig" \
|
||||
CGO_LDFLAGS="-L$CHROOT/usr/lib64 -L$CHROOT/lib64" \
|
||||
CGO_CFLAGS="-I$CHROOT/usr/include" \
|
||||
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o build/cam2ip.linux.amd64 -ldflags "-linkmode external -s -w" github.com/gen2brain/cam2ip
|
||||
|
||||
PKG_CONFIG="/usr/bin/i686-w64-mingw32-pkg-config" \
|
||||
PKG_CONFIG_PATH="$MINGW/usr/lib/pkgconfig" \
|
||||
PKG_CONFIG_LIBDIR="$MINGW/usr/lib/pkgconfig" \
|
||||
CGO_LDFLAGS="-L$MINGW/usr/lib" \
|
||||
CGO_CFLAGS="-I$MINGW/usr/include" \
|
||||
CC="i686-w64-mingw32-gcc" CXX="i686-w64-mingw32-g++" \
|
||||
CGO_ENABLED=1 GOOS=windows GOARCH=386 go build -tags cv2 -o build/cam2ip.386.cv2.exe -ldflags "-linkmode external -s -w '-extldflags=-static'" github.com/gen2brain/cam2ip
|
||||
|
||||
PKG_CONFIG="/usr/bin/i686-w64-mingw32-pkg-config" \
|
||||
PKG_CONFIG_PATH="$MINGW/usr/lib/pkgconfig" \
|
||||
PKG_CONFIG_LIBDIR="$MINGW/usr/lib/pkgconfig" \
|
||||
CGO_LDFLAGS="-L$MINGW/usr/lib" \
|
||||
CGO_CFLAGS="-I$MINGW/usr/include" \
|
||||
CC="i686-w64-mingw32-gcc" CXX="i686-w64-mingw32-g++" \
|
||||
CGO_ENABLED=1 GOOS=windows GOARCH=386 go build -o build/cam2ip.386.exe -ldflags "-linkmode external -s -w '-extldflags=-static'" github.com/gen2brain/cam2ip
|
||||
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o build/cam2ip.linux.amd64 -ldflags "-linkmode external -s -w" github.com/gen2brain/cam2ip/cmd/cam2ip
|
||||
|
||||
PKG_CONFIG="/usr/bin/x86_64-w64-mingw32-pkg-config" \
|
||||
PKG_CONFIG_PATH="$MINGW64/usr/lib/pkgconfig" \
|
||||
@@ -44,7 +30,7 @@ PKG_CONFIG_LIBDIR="$MINGW64/usr/lib/pkgconfig" \
|
||||
CGO_LDFLAGS="-L$MINGW64/usr/lib" \
|
||||
CGO_CFLAGS="-I$MINGW64/usr/include" \
|
||||
CC="x86_64-w64-mingw32-gcc" CXX="x86_64-w64-mingw32-g++" \
|
||||
CGO_ENABLED=1 GOOS=windows GOARCH=amd64 go build -tags cv2 -o build/cam2ip.amd64.cv2.exe -ldflags "-linkmode external -s -w '-extldflags=-static'" github.com/gen2brain/cam2ip
|
||||
CGO_ENABLED=1 GOOS=windows GOARCH=amd64 go build -tags cv2,turbo,pkgconfig -o build/cam2ip.amd64.cv2.exe -ldflags "-linkmode external -s -w '-extldflags=-static'" github.com/gen2brain/cam2ip/cmd/cam2ip
|
||||
|
||||
PKG_CONFIG="/usr/bin/x86_64-w64-mingw32-pkg-config" \
|
||||
PKG_CONFIG_PATH="$MINGW64/usr/lib/pkgconfig" \
|
||||
@@ -52,31 +38,7 @@ PKG_CONFIG_LIBDIR="$MINGW64/usr/lib/pkgconfig" \
|
||||
CGO_LDFLAGS="-L$MINGW64/usr/lib" \
|
||||
CGO_CFLAGS="-I$MINGW64/usr/include" \
|
||||
CC="x86_64-w64-mingw32-gcc" CXX="x86_64-w64-mingw32-g++" \
|
||||
CGO_ENABLED=1 GOOS=windows GOARCH=amd64 go build -o build/cam2ip.amd64.exe -ldflags "-linkmode external -s -w '-extldflags=-static'" github.com/gen2brain/cam2ip
|
||||
|
||||
PKG_CONFIG="/usr/bin/armv6j-hardfloat-linux-gnueabi-pkg-config" \
|
||||
PKG_CONFIG_PATH="$RPI/usr/lib/pkgconfig" \
|
||||
PKG_CONFIG_LIBDIR="$RPI/usr/lib/pkgconfig" \
|
||||
CGO_LDFLAGS="-L$RPI/usr/lib" \
|
||||
CGO_CFLAGS="-I$RPI/usr/include" \
|
||||
CC="armv6j-hardfloat-linux-gnueabi-gcc" CXX="armv6j-hardfloat-linux-gnueabi-g++" \
|
||||
CGO_ENABLED=1 GOOS=linux GOARCH=arm go build -tags cv2 -o build/cam2ip.linux.arm.cv2 -ldflags "-linkmode external -s -w" github.com/gen2brain/cam2ip
|
||||
|
||||
PKG_CONFIG="/usr/bin/armv6j-hardfloat-linux-gnueabi-pkg-config" \
|
||||
PKG_CONFIG_PATH="$RPI/usr/lib/pkgconfig" \
|
||||
PKG_CONFIG_LIBDIR="$RPI/usr/lib/pkgconfig" \
|
||||
CGO_LDFLAGS="-L$RPI/usr/lib" \
|
||||
CGO_CFLAGS="-I$RPI/usr/include" \
|
||||
CC="armv6j-hardfloat-linux-gnueabi-gcc" CXX="armv6j-hardfloat-linux-gnueabi-g++" \
|
||||
CGO_ENABLED=1 GOOS=linux GOARCH=arm go build -o build/cam2ip.linux.arm -ldflags "-linkmode external -s -w" github.com/gen2brain/cam2ip
|
||||
|
||||
PKG_CONFIG="/usr/bin/armv6j-hardfloat-linux-gnueabi-pkg-config" \
|
||||
PKG_CONFIG_PATH="$RPI/usr/lib/pkgconfig" \
|
||||
PKG_CONFIG_LIBDIR="$RPI/usr/lib/pkgconfig" \
|
||||
CGO_LDFLAGS="-L$RPI/usr/lib" \
|
||||
CGO_CFLAGS="-I$RPI/usr/include" \
|
||||
CC="armv6j-hardfloat-linux-gnueabi-gcc" CXX="armv6j-hardfloat-linux-gnueabi-g++" \
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -tags jpeg -o build/cam2ip.linux.arm.nocgo -ldflags "-s -w" github.com/gen2brain/cam2ip
|
||||
CGO_ENABLED=1 GOOS=windows GOARCH=amd64 go build -o build/cam2ip.amd64.exe -ldflags "-linkmode external -s -w '-extldflags=-static'" github.com/gen2brain/cam2ip/cmd/cam2ip
|
||||
|
||||
PKG_CONFIG="/usr/bin/armv7a-hardfloat-linux-gnueabi-pkg-config" \
|
||||
PKG_CONFIG_PATH="$RPI3/usr/lib/pkgconfig" \
|
||||
@@ -84,7 +46,7 @@ PKG_CONFIG_LIBDIR="$RPI3/usr/lib/pkgconfig" \
|
||||
CGO_LDFLAGS="-L$RPI3/usr/lib" \
|
||||
CGO_CFLAGS="-I$RPI3/usr/include" \
|
||||
CC="armv7a-hardfloat-linux-gnueabi-gcc" CXX="armv7a-hardfloat-linux-gnueabi-g++" \
|
||||
CGO_ENABLED=1 GOOS=linux GOARCH=arm go build -tags cv2 -o build/cam2ip.linux.arm7.cv2 -ldflags "-linkmode external -s -w" github.com/gen2brain/cam2ip
|
||||
CGO_ENABLED=1 GOOS=linux GOARCH=arm go build -tags cv2,turbo -o build/cam2ip.linux.arm7.cv2 -ldflags "-linkmode external -s -w" github.com/gen2brain/cam2ip/cmd/cam2ip
|
||||
|
||||
PKG_CONFIG="/usr/bin/armv7a-hardfloat-linux-gnueabi-pkg-config" \
|
||||
PKG_CONFIG_PATH="$RPI3/usr/lib/pkgconfig" \
|
||||
@@ -92,4 +54,18 @@ PKG_CONFIG_LIBDIR="$RPI3/usr/lib/pkgconfig" \
|
||||
CGO_LDFLAGS="-L$RPI3/usr/lib" \
|
||||
CGO_CFLAGS="-I$RPI3/usr/include" \
|
||||
CC="armv7a-hardfloat-linux-gnueabi-gcc" CXX="armv7a-hardfloat-linux-gnueabi-g++" \
|
||||
CGO_ENABLED=1 GOOS=linux GOARCH=arm go build -o build/cam2ip.linux.arm7 -ldflags "-linkmode external -s -w" github.com/gen2brain/cam2ip
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -o build/cam2ip.linux.arm7 -ldflags "-linkmode external -s -w" github.com/gen2brain/cam2ip/cmd/cam2ip
|
||||
|
||||
PKG_CONFIG_PATH="$APPLE/SDK/MacOSX10.10.sdk/usr/lib/pkgconfig" \
|
||||
PKG_CONFIG_LIBDIR="$APPLE/SDK/MacOSX10.10.sdk/usr/lib/pkgconfig" \
|
||||
CGO_LDFLAGS="-L$APPLE/SDK/MacOSX10.10.sdk/usr/lib -mmacosx-version-min=10.10" \
|
||||
CGO_CFLAGS="-I$APPLE/SDK/MacOSX10.10.sdk/usr/include -mmacosx-version-min=10.10" \
|
||||
CC="$APPLE/bin/x86_64-apple-darwin14-clang" CXX="$APPLE/bin/x86_64-apple-darwin14-clang++" \
|
||||
CGO_ENABLED=1 GOOS=darwin GOARCH=amd64 go build -tags cv2,turbo -o build/cam2ip.darwin.amd64 -ldflags "-linkmode external -s -w '-extldflags=-mmacosx-version-min=10.10'" github.com/gen2brain/cam2ip/cmd/cam2ip
|
||||
|
||||
PKG_CONFIG_PATH="$ANDROID/lib/pkgconfig" \
|
||||
PKG_CONFIG_LIBDIR="$ANDROID/lib/pkgconfig" \
|
||||
CGO_LDFLAGS="-L$ANDROID/sysroot/usr/lib" \
|
||||
CGO_CFLAGS="-I$ANDROID/sysroot/usr/include --sysroot=$ANDROID/sysroot" \
|
||||
CC="$ANDROID/bin/arm-linux-androideabi-clang" CXX="$ANDROID/bin/arm-linux-androideabi-clang++" \
|
||||
CGO_ENABLED=1 GOOS=android GOARCH=arm go build -o build/cam2ip.android.arm7 -ldflags "-linkmode external -s -w" github.com/gen2brain/cam2ip/cmd/cam2ip
|
||||
|
||||
@@ -28,7 +28,8 @@ type Server struct {
|
||||
|
||||
Rotate int
|
||||
|
||||
NoWebGL bool
|
||||
NoWebGL bool
|
||||
Timestamp bool
|
||||
|
||||
FileName string
|
||||
|
||||
@@ -49,23 +50,20 @@ func (s *Server) ListenAndServe() error {
|
||||
basic = auth.NewBasicAuthenticator(realm, auth.HtpasswdFileProvider(s.Htpasswd))
|
||||
}
|
||||
|
||||
http.Handle("/html", newAuthHandler(handlers.NewHTML(s.Bind, s.FrameWidth, s.FrameHeight, s.NoWebGL), basic))
|
||||
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", handlers.NewSocket(s.Reader, s.Delay))
|
||||
http.Handle("/socket", newAuthHandler(handlers.NewSocket(s.Reader, s.Delay), basic))
|
||||
|
||||
http.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
http.Handle("/", newAuthHandler(handlers.NewIndex(), basic))
|
||||
|
||||
srv := &http.Server{}
|
||||
|
||||
listener, err := net.Listen("tcp4", s.Bind)
|
||||
listener, err := net.Listen("tcp", s.Bind)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
// +build cv2,!cv3
|
||||
|
||||
// Package video.
|
||||
package video
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
|
||||
"github.com/disintegration/imaging"
|
||||
"github.com/lazywei/go-opencv/opencv"
|
||||
)
|
||||
|
||||
// Options.
|
||||
type Options struct {
|
||||
Filename string
|
||||
Rotate int
|
||||
}
|
||||
|
||||
// Video represents video.
|
||||
type Video struct {
|
||||
opts Options
|
||||
video *opencv.Capture
|
||||
frame *opencv.IplImage
|
||||
}
|
||||
|
||||
// New returns new Video for given path.
|
||||
func New(opts Options) (video *Video, err error) {
|
||||
video = &Video{}
|
||||
video.opts = opts
|
||||
|
||||
video.video = opencv.NewFileCapture(opts.Filename)
|
||||
if video.video == nil {
|
||||
err = fmt.Errorf("video: can not open video %s", opts.Filename)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Read reads next frame from video and returns image.
|
||||
func (v *Video) Read() (img image.Image, err error) {
|
||||
if v.video.GrabFrame() {
|
||||
v.frame = v.video.RetrieveFrame(1)
|
||||
if v.frame == nil {
|
||||
err = fmt.Errorf("video: can not retrieve frame")
|
||||
return
|
||||
}
|
||||
|
||||
img = v.frame.ToImage()
|
||||
if v.opts.Rotate == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
switch v.opts.Rotate {
|
||||
case 90:
|
||||
img = imaging.Rotate90(img)
|
||||
case 180:
|
||||
img = imaging.Rotate180(img)
|
||||
case 270:
|
||||
img = imaging.Rotate270(img)
|
||||
}
|
||||
} else {
|
||||
err = fmt.Errorf("video: can not grab frame")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Close closes video.
|
||||
func (v *Video) Close() (err error) {
|
||||
if v.video == nil {
|
||||
err = fmt.Errorf("video: video is not opened")
|
||||
return
|
||||
}
|
||||
|
||||
v.frame.Release()
|
||||
v.video.Release()
|
||||
v.video = nil
|
||||
return
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
// +build cv3,!cv2
|
||||
|
||||
// Package video.
|
||||
package video
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
|
||||
"github.com/disintegration/imaging"
|
||||
"gocv.io/x/gocv"
|
||||
)
|
||||
|
||||
// Options.
|
||||
type Options struct {
|
||||
Filename string
|
||||
Rotate int
|
||||
}
|
||||
|
||||
// Video represents video.
|
||||
type Video struct {
|
||||
opts Options
|
||||
video *gocv.VideoCapture
|
||||
frame *gocv.Mat
|
||||
}
|
||||
|
||||
// New returns new Video for given path.
|
||||
func New(opts Options) (video *Video, err error) {
|
||||
video = &Video{}
|
||||
video.opts = opts
|
||||
|
||||
mat := gocv.NewMat()
|
||||
video.frame = &mat
|
||||
|
||||
video.video, err = gocv.VideoCaptureFile(opts.Filename)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("video: can not open video %s: %s", opts.Filename, err.Error())
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Read reads next frame from video and returns image.
|
||||
func (v *Video) Read() (img image.Image, err error) {
|
||||
ok := v.video.Read(v.frame)
|
||||
if !ok {
|
||||
err = fmt.Errorf("video: can not grab frame")
|
||||
return
|
||||
}
|
||||
|
||||
if v.frame == nil {
|
||||
err = fmt.Errorf("video: can not retrieve frame")
|
||||
return
|
||||
}
|
||||
|
||||
img, e := v.frame.ToImage()
|
||||
if e != nil {
|
||||
err = fmt.Errorf("video: %v", e)
|
||||
return
|
||||
}
|
||||
|
||||
if v.opts.Rotate == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
switch v.opts.Rotate {
|
||||
case 90:
|
||||
img = imaging.Rotate90(img)
|
||||
case 180:
|
||||
img = imaging.Rotate180(img)
|
||||
case 270:
|
||||
img = imaging.Rotate270(img)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Close closes video.
|
||||
func (v *Video) Close() (err error) {
|
||||
if v.video == nil {
|
||||
err = fmt.Errorf("video: video is not opened")
|
||||
return
|
||||
}
|
||||
|
||||
v.frame.Close()
|
||||
err = v.video.Close()
|
||||
v.video = nil
|
||||
return
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
package video
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image/jpeg"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestVideo(t *testing.T) {
|
||||
video, err := New(video.Options{"test.mp4", 0})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer video.Close()
|
||||
|
||||
tmpdir, err := ioutil.TempDir(os.TempDir(), "cam2ip")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
var i int
|
||||
var n int = 10
|
||||
|
||||
timeout := time.After(time.Duration(n) * time.Second)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-timeout:
|
||||
//fmt.Printf("Fps: %d\n", i/n)
|
||||
return
|
||||
default:
|
||||
i += 1
|
||||
|
||||
img, err := video.Read()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
file, err := os.Create(filepath.Join(tmpdir, fmt.Sprintf("%03d.jpg", i)))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
err = jpeg.Encode(file, img, &jpeg.Options{Quality: 75})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
err = file.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user