mirror of
https://github.com/kerberos-io/agent.git
synced 2026-03-02 22:59:15 +00:00
Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
192f78ae78 | ||
|
|
ef20d4c0b1 | ||
|
|
af95c0f798 | ||
|
|
0e32a10ff5 | ||
|
|
e59c2b179d | ||
|
|
e5d03f19de | ||
|
|
58c3e73f6f | ||
|
|
8ca2c44422 | ||
|
|
a2e584a225 | ||
|
|
c4cda0afb0 | ||
|
|
adbb923e92 | ||
|
|
f444ae4ad6 | ||
|
|
9fa9538320 | ||
|
|
943e81000e | ||
|
|
b16d028293 | ||
|
|
07646e483d | ||
|
|
36b93a34b4 | ||
|
|
b0d2409524 | ||
|
|
be7a231950 | ||
|
|
31a0b9efa4 | ||
|
|
d70a3ed343 | ||
|
|
56cebb6451 | ||
|
|
99f61bc5e8 | ||
|
|
a5d02e3275 | ||
|
|
354ab7db05 | ||
|
|
dc817f8c26 | ||
|
|
a90097731c | ||
|
|
5b3bbbb37e | ||
|
|
4a4aabd71c | ||
|
|
b058c1e742 | ||
|
|
7671b1c2c3 | ||
|
|
4cc8135e1a | ||
|
|
3cb38099ea | ||
|
|
deb0308dc4 | ||
|
|
24c729eea3 | ||
|
|
c59d511ea3 | ||
|
|
6f8745dc3a | ||
|
|
65d3d649b9 | ||
|
|
b4a8028c04 |
@@ -1,2 +1,2 @@
|
||||
FROM kerberos/devcontainer:9da0ee3
|
||||
FROM kerberos/devcontainer:b2bc659
|
||||
LABEL AUTHOR=Kerberos.io
|
||||
|
||||
3
.github/workflows/docker-dev.yml
vendored
3
.github/workflows/docker-dev.yml
vendored
@@ -34,7 +34,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
architecture: [arm64, arm/v7, arm/v6]
|
||||
#architecture: [arm64, arm/v7, arm/v6]
|
||||
architecture: [arm64, arm/v7]
|
||||
steps:
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v2
|
||||
|
||||
1
.github/workflows/docker.yml
vendored
1
.github/workflows/docker.yml
vendored
@@ -62,6 +62,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
architecture: [arm64, arm-v7, arm-v6]
|
||||
#architecture: [arm64, arm-v7]
|
||||
steps:
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v2
|
||||
|
||||
37
README.md
37
README.md
@@ -210,6 +210,43 @@ After a few minutes, you will see a beautiful `Visual Studio Code` shown in your
|
||||
|
||||

|
||||
|
||||
On opening of the GitHub Codespace, some dependencies will be installed. Once this is done go ahead to the `ui/src/config.json` file, and (un)comment following section. Make sure to replace the `externalHost` variable with the DNS name you will retrieve from the next step.
|
||||
|
||||
// Uncomment this when using codespaces or other special DNS names (which you can't control)
|
||||
// replace this with the DNS name of the kerberos agent server (the codespace url)
|
||||
const externalHost = 'cedricve-automatic-computing-machine-v647rxvj4whx9qp-80.preview.app.github.dev';
|
||||
|
||||
const dev = {
|
||||
ENV: 'dev',
|
||||
HOSTNAME: externalHost,
|
||||
//API_URL: `${protocol}//${hostname}:8080/api`,
|
||||
//URL: `${protocol}//${hostname}:8080`,
|
||||
//WS_URL: `${websocketprotocol}//${hostname}:8080/ws`,
|
||||
|
||||
// Uncomment, and comment the above lines, when using codespaces or other special DNS names (which you can't control)
|
||||
API_URL: `${protocol}//${externalHost}/api`,
|
||||
URL: `${protocol}//${externalHost}`,
|
||||
WS_URL: `${websocketprotocol}//${externalHost}/ws`,
|
||||
};
|
||||
|
||||
Go and open two terminals one for the `ui` project and one for the `machinery` project.
|
||||
|
||||
1. Terminal A:
|
||||
|
||||
cd machinery/
|
||||
go run main.go run camera 80
|
||||
|
||||
2. Terminal B:
|
||||
|
||||
cd ui/
|
||||
yarn start
|
||||
|
||||
Once executed, a popup will show up mentioning `portforwarding`. You should see two ports being opened, one for the ui `3000` and one for the machinery `80`. `Right-click` on the port `80` and change visibility from `private` to `public`, this is required to avoid `CORS` errors.
|
||||
|
||||

|
||||
|
||||
As mentioned above, copy the hostname of the `machinery` DNS name, and past it in the `ui/src/config.json` file. Once done reload, the `ui` page in your browser, and you should be able to access the login page with the default credentials `root` and `root`.
|
||||
|
||||
## Develop and build
|
||||
|
||||
Kerberos Agent is divided in two parts a `machinery` and `web`. Both parts live in this repository in their relative folders. For development or running the application on your local machine, you have to run both the `machinery` and the `web` as described below. When running in production everything is shipped as only one artifact, read more about this at [Building for production](#building-for-production).
|
||||
|
||||
BIN
assets/img/codespace-make-public.png
Normal file
BIN
assets/img/codespace-make-public.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 138 KiB |
@@ -1,3 +1,4 @@
|
||||
|
||||
{
|
||||
"type": "",
|
||||
"key": "",
|
||||
@@ -21,6 +22,10 @@
|
||||
"device": ""
|
||||
},
|
||||
"continuous": "false",
|
||||
"recording": "true",
|
||||
"snapshots": "true",
|
||||
"liveview": "true",
|
||||
"motion": "true",
|
||||
"postrecording": 20,
|
||||
"prerecording": 10,
|
||||
"maxlengthrecording": 30,
|
||||
|
||||
@@ -2,7 +2,8 @@ module github.com/kerberos-io/agent/machinery
|
||||
|
||||
go 1.19
|
||||
|
||||
//replace github.com/kerberos-io/joy4 v1.0.51 => ../../../../github.com/kerberos-io/joy4
|
||||
//replace github.com/kerberos-io/joy4 v1.0.54 => ../../../../github.com/kerberos-io/joy4
|
||||
|
||||
//replace github.com/kerberos-io/onvif v0.0.5 => ../../../../github.com/kerberos-io/onvif
|
||||
|
||||
require (
|
||||
@@ -20,7 +21,7 @@ require (
|
||||
github.com/golang-module/carbon/v2 v2.2.3
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/kellydunn/golang-geo v0.7.0
|
||||
github.com/kerberos-io/joy4 v1.0.53
|
||||
github.com/kerberos-io/joy4 v1.0.55
|
||||
github.com/kerberos-io/onvif v0.0.5
|
||||
github.com/minio/minio-go/v6 v6.0.57
|
||||
github.com/nsmith5/mjpeg v0.0.0-20200913181537-54b8ada0e53e
|
||||
|
||||
@@ -175,8 +175,8 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/kellydunn/golang-geo v0.7.0 h1:A5j0/BvNgGwY6Yb6inXQxzYwlPHc6WVZR+MrarZYNNg=
|
||||
github.com/kellydunn/golang-geo v0.7.0/go.mod h1:YYlQPJ+DPEzrHx8kT3oPHC/NjyvCCXE+IuKGKdrjrcU=
|
||||
github.com/kerberos-io/joy4 v1.0.53 h1:DfVptCUzo/77xLUIwnp1/dbcVffmT0DKPDduQBcu26Y=
|
||||
github.com/kerberos-io/joy4 v1.0.53/go.mod h1:nZp4AjvKvTOXRrmDyAIOw+Da+JA5OcSo/JundGfOlFU=
|
||||
github.com/kerberos-io/joy4 v1.0.55 h1:P5RISBp8kUowgb/bvqLPVKPJL9n9jI/wXBCLs+XFMWg=
|
||||
github.com/kerberos-io/joy4 v1.0.55/go.mod h1:nZp4AjvKvTOXRrmDyAIOw+Da+JA5OcSo/JundGfOlFU=
|
||||
github.com/kerberos-io/onvif v0.0.5 h1:kq9mnHZkih9Jl4DyIJ4Rzt++Y3DDKy3nI8S2ESEfZ5w=
|
||||
github.com/kerberos-io/onvif v0.0.5/go.mod h1:Hr2dJOH2LM5SpYKk17gYZ1CMjhGhUl+QlT5kwYogrW0=
|
||||
github.com/klauspost/cpuid v1.2.3 h1:CCtW0xUnWGVINKvE/WWOYKdsPV6mawAtvQuSl8guwQs=
|
||||
|
||||
@@ -25,7 +25,19 @@ func OpenRTSP(url string) (av.DemuxCloser, []av.CodecData, error) {
|
||||
return nil, []av.CodecData{}, err
|
||||
}
|
||||
|
||||
func GetVideoDecoder(streams []av.CodecData) *ffmpeg.VideoDecoder {
|
||||
func GetVideoStream(streams []av.CodecData) (av.CodecData, error) {
|
||||
var videoStream av.CodecData
|
||||
for _, stream := range streams {
|
||||
if stream.Type().IsAudio() {
|
||||
//astream := stream.(av.AudioCodecData)
|
||||
} else if stream.Type().IsVideo() {
|
||||
videoStream = stream
|
||||
}
|
||||
}
|
||||
return videoStream, nil
|
||||
}
|
||||
|
||||
func GetVideoDecoder(decoder *ffmpeg.VideoDecoder, streams []av.CodecData) {
|
||||
// Load video codec
|
||||
var vstream av.VideoCodecData
|
||||
for _, stream := range streams {
|
||||
@@ -35,8 +47,10 @@ func GetVideoDecoder(streams []av.CodecData) *ffmpeg.VideoDecoder {
|
||||
vstream = stream.(av.VideoCodecData)
|
||||
}
|
||||
}
|
||||
dec, _ := ffmpeg.NewVideoDecoder(vstream)
|
||||
return dec
|
||||
err := ffmpeg.NewVideoDecoder(decoder, vstream)
|
||||
if err != nil {
|
||||
log.Log.Error("GetVideoDecoder: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func DecodeImage(frame *ffmpeg.VideoFrame, pkt av.Packet, decoder *ffmpeg.VideoDecoder, decoderMutex *sync.Mutex) (*ffmpeg.VideoFrame, error) {
|
||||
@@ -46,6 +60,60 @@ func DecodeImage(frame *ffmpeg.VideoFrame, pkt av.Packet, decoder *ffmpeg.VideoD
|
||||
return img, err
|
||||
}
|
||||
|
||||
func GetStreamInsights(infile av.DemuxCloser, streams []av.CodecData) (int, int, int, int) {
|
||||
var width, height, fps, gopsize int
|
||||
for _, stream := range streams {
|
||||
if stream.Type().IsAudio() {
|
||||
//astream := stream.(av.AudioCodecData)
|
||||
} else if stream.Type().IsVideo() {
|
||||
vstream := stream.(av.VideoCodecData)
|
||||
width = vstream.Width()
|
||||
height = vstream.Height()
|
||||
}
|
||||
}
|
||||
|
||||
loop:
|
||||
for timeout := time.After(1 * time.Second); ; {
|
||||
var err error
|
||||
if _, err = infile.ReadPacket(); err != nil { // sometimes this throws an end of file..
|
||||
log.Log.Error("HandleStream: " + err.Error())
|
||||
}
|
||||
fps++
|
||||
select {
|
||||
case <-timeout:
|
||||
break loop
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
gopCounter := 0
|
||||
start := false
|
||||
for {
|
||||
var pkt av.Packet
|
||||
var err error
|
||||
if pkt, err = infile.ReadPacket(); err != nil { // sometimes this throws an end of file..
|
||||
log.Log.Error("HandleStream: " + err.Error())
|
||||
}
|
||||
// Could be that a decode is throwing errors.
|
||||
if len(pkt.Data) > 0 {
|
||||
if start {
|
||||
gopCounter = gopCounter + 1
|
||||
}
|
||||
|
||||
if pkt.IsKeyFrame {
|
||||
if start == false {
|
||||
start = true
|
||||
} else {
|
||||
gopsize = gopCounter
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return width, height, fps, gopsize
|
||||
}
|
||||
|
||||
func HandleStream(infile av.DemuxCloser, queue *pubsub.Queue, communication *models.Communication) { //, wg *sync.WaitGroup) {
|
||||
|
||||
log.Log.Debug("HandleStream: started")
|
||||
|
||||
@@ -272,12 +272,30 @@ func HandleRecordStream(queue *pubsub.Queue, configuration *models.Configuration
|
||||
var file *os.File
|
||||
var err error
|
||||
|
||||
var lastDuration time.Duration
|
||||
var lastRecordingTime int64
|
||||
|
||||
for motion := range communication.HandleMotion {
|
||||
|
||||
timestamp = time.Now().Unix()
|
||||
startRecording = time.Now().Unix() // we mark the current time when the record started.
|
||||
numberOfChanges := motion.NumberOfChanges
|
||||
|
||||
// If we have prerecording we will substract the number of seconds.
|
||||
// Taking into account FPS = GOP size (Keyfram interval)
|
||||
if config.Capture.PreRecording > 0 {
|
||||
|
||||
// Might be that recordings are coming short after each other.
|
||||
// Therefore we do some math with the current time and the last recording time.
|
||||
|
||||
timeBetweenNowAndLastRecording := startRecording - lastRecordingTime
|
||||
if timeBetweenNowAndLastRecording > int64(config.Capture.PreRecording) {
|
||||
startRecording = startRecording - int64(config.Capture.PreRecording) + 1
|
||||
} else {
|
||||
startRecording = startRecording - timeBetweenNowAndLastRecording
|
||||
}
|
||||
}
|
||||
|
||||
// timestamp_microseconds_instanceName_regionCoordinates_numberOfChanges_token
|
||||
// 1564859471_6-474162_oprit_577-283-727-375_1153_27.mp4
|
||||
// - Timestamp
|
||||
@@ -318,7 +336,7 @@ func HandleRecordStream(queue *pubsub.Queue, configuration *models.Configuration
|
||||
var cursorError error
|
||||
var pkt av.Packet
|
||||
var nextPkt av.Packet
|
||||
recordingCursor := queue.Oldest()
|
||||
recordingCursor := queue.DelayedGopCount(int(config.Capture.PreRecording))
|
||||
|
||||
if cursorError == nil {
|
||||
pkt, cursorError = recordingCursor.ReadPacket()
|
||||
@@ -345,7 +363,7 @@ func HandleRecordStream(queue *pubsub.Queue, configuration *models.Configuration
|
||||
log.Log.Info("HandleRecordStream: closing recording (timestamp: " + strconv.FormatInt(timestamp, 10) + ", recordingPeriod: " + strconv.FormatInt(recordingPeriod, 10) + ", now: " + strconv.FormatInt(now, 10) + ", startRecording: " + strconv.FormatInt(startRecording, 10) + ", maxRecordingPeriod: " + strconv.FormatInt(maxRecordingPeriod, 10))
|
||||
break
|
||||
}
|
||||
if pkt.IsKeyFrame && !start {
|
||||
if pkt.IsKeyFrame && !start && pkt.Time >= lastDuration {
|
||||
log.Log.Info("HandleRecordStream: write frames")
|
||||
start = true
|
||||
}
|
||||
@@ -372,6 +390,9 @@ func HandleRecordStream(queue *pubsub.Queue, configuration *models.Configuration
|
||||
myMuxer.WriteTrailerWithPacket(nextPkt)
|
||||
log.Log.Info("HandleRecordStream: file save: " + name)
|
||||
|
||||
lastDuration = pkt.Time
|
||||
lastRecordingTime = time.Now().Unix()
|
||||
|
||||
// Cleanup muxer
|
||||
myMuxer.Close()
|
||||
myMuxer = nil
|
||||
|
||||
@@ -95,6 +95,9 @@ func GetSystemInfo() (models.System, error) {
|
||||
var usedMem uint64 = 0
|
||||
var totalMem uint64 = 0
|
||||
var freeMem uint64 = 0
|
||||
|
||||
var processUsedMem uint64 = 0
|
||||
|
||||
architecture := ""
|
||||
cpuId := ""
|
||||
KernelVersion := ""
|
||||
@@ -133,18 +136,27 @@ func GetSystemInfo() (models.System, error) {
|
||||
}
|
||||
}
|
||||
|
||||
process, err := sysinfo.Self()
|
||||
if err == nil {
|
||||
memInfo, err := process.Memory()
|
||||
if err == nil {
|
||||
processUsedMem = memInfo.Resident
|
||||
}
|
||||
}
|
||||
|
||||
system := models.System{
|
||||
Hostname: hostname,
|
||||
CPUId: cpuId,
|
||||
KernelVersion: KernelVersion,
|
||||
Version: agentVersion,
|
||||
MACs: MACs,
|
||||
IPs: IPs,
|
||||
BootTime: uint64(bootTime.Unix()),
|
||||
Architecture: architecture,
|
||||
UsedMemory: usedMem,
|
||||
TotalMemory: totalMem,
|
||||
FreeMemory: freeMem,
|
||||
Hostname: hostname,
|
||||
CPUId: cpuId,
|
||||
KernelVersion: KernelVersion,
|
||||
Version: agentVersion,
|
||||
MACs: MACs,
|
||||
IPs: IPs,
|
||||
BootTime: uint64(bootTime.Unix()),
|
||||
Architecture: architecture,
|
||||
UsedMemory: usedMem,
|
||||
TotalMemory: totalMem,
|
||||
FreeMemory: freeMem,
|
||||
ProcessUsedMemory: processUsedMem,
|
||||
}
|
||||
|
||||
return system, nil
|
||||
@@ -200,6 +212,11 @@ func HandleHeartBeat(configuration *models.Configuration, communication *models.
|
||||
uptimeString := carbon.Parse(uptimeFormatted).DiffForHumans()
|
||||
uptimeString = strings.ReplaceAll(uptimeString, "ago", "")
|
||||
|
||||
// Do the same for boottime
|
||||
bootTimeFormatted := time.Unix(int64(system.BootTime), 0).Format("2006-01-02 15:04:05")
|
||||
boottimeString := carbon.Parse(bootTimeFormatted).DiffForHumans()
|
||||
boottimeString = strings.ReplaceAll(boottimeString, "ago", "")
|
||||
|
||||
// We'll check which mode is enabled for the camera.
|
||||
onvifEnabled := "false"
|
||||
if config.Capture.IPCamera.ONVIFXAddr != "" {
|
||||
@@ -221,6 +238,10 @@ func HandleHeartBeat(configuration *models.Configuration, communication *models.
|
||||
isEnterprise = true
|
||||
}
|
||||
|
||||
// Congert to string
|
||||
macs, _ := json.Marshal(system.MACs)
|
||||
ips, _ := json.Marshal(system.IPs)
|
||||
|
||||
var object = fmt.Sprintf(`{
|
||||
"key" : "%s",
|
||||
"version" : "3.0.0",
|
||||
@@ -235,13 +256,15 @@ func HandleHeartBeat(configuration *models.Configuration, communication *models.
|
||||
"totalMemory" : "%d",
|
||||
"usedMemory" : "%d",
|
||||
"freeMemory" : "%d",
|
||||
"macs" : "%v",
|
||||
"ips" : "%v",
|
||||
"processMemory" : "%d",
|
||||
"mac_list" : %s,
|
||||
"ip_list" : %s,
|
||||
"board" : "",
|
||||
"disk1size" : "%s",
|
||||
"disk3size" : "%s",
|
||||
"diskvdasize" : "%s",
|
||||
"uptime" : "%s",
|
||||
"boot_time" : "%s",
|
||||
"siteID" : "%s",
|
||||
"onvif" : "%s",
|
||||
"numberoffiles" : "33",
|
||||
@@ -250,7 +273,7 @@ func HandleHeartBeat(configuration *models.Configuration, communication *models.
|
||||
"docker" : true,
|
||||
"kios" : false,
|
||||
"raspberrypi" : false
|
||||
}`, config.Key, system.Version, system.CPUId, username, key, name, isEnterprise, system.Hostname, system.Architecture, system.TotalMemory, system.UsedMemory, system.FreeMemory, system.MACs, system.IPs, "0", "0", "0", uptimeString, config.HubSite, onvifEnabled)
|
||||
}`, config.Key, system.Version, system.CPUId, username, key, name, isEnterprise, system.Hostname, system.Architecture, system.TotalMemory, system.UsedMemory, system.FreeMemory, system.ProcessUsedMemory, macs, ips, "0", "0", "0", uptimeString, boottimeString, config.HubSite, onvifEnabled)
|
||||
|
||||
var jsonStr = []byte(object)
|
||||
buffy := bytes.NewBuffer(jsonStr)
|
||||
@@ -308,51 +331,59 @@ func HandleLiveStreamSD(livestreamCursor *pubsub.QueueCursor, configuration *mod
|
||||
|
||||
config := configuration.Config
|
||||
|
||||
// If offline made is enabled, we will stop the thread.
|
||||
if config.Offline == "true" {
|
||||
log.Log.Debug("HandleLiveStreamSD: stopping as Offline is enabled.")
|
||||
} else {
|
||||
|
||||
// Allocate frame
|
||||
frame := ffmpeg.AllocVideoFrame()
|
||||
// Check if we need to enable the live stream
|
||||
if config.Capture.Liveview != "false" {
|
||||
|
||||
key := ""
|
||||
if config.Cloud == "s3" && config.S3 != nil && config.S3.Publickey != "" {
|
||||
key = config.S3.Publickey
|
||||
} else if config.Cloud == "kstorage" && config.KStorage != nil && config.KStorage.CloudKey != "" {
|
||||
key = config.KStorage.CloudKey
|
||||
}
|
||||
// This is the new way ;)
|
||||
if config.HubKey != "" {
|
||||
key = config.HubKey
|
||||
}
|
||||
// Allocate frame
|
||||
frame := ffmpeg.AllocVideoFrame()
|
||||
|
||||
topic := "kerberos/" + key + "/device/" + config.Key + "/live"
|
||||
|
||||
lastLivestreamRequest := int64(0)
|
||||
|
||||
var cursorError error
|
||||
var pkt av.Packet
|
||||
|
||||
for cursorError == nil {
|
||||
pkt, cursorError = livestreamCursor.ReadPacket()
|
||||
if len(pkt.Data) == 0 || !pkt.IsKeyFrame {
|
||||
continue
|
||||
key := ""
|
||||
if config.Cloud == "s3" && config.S3 != nil && config.S3.Publickey != "" {
|
||||
key = config.S3.Publickey
|
||||
} else if config.Cloud == "kstorage" && config.KStorage != nil && config.KStorage.CloudKey != "" {
|
||||
key = config.KStorage.CloudKey
|
||||
}
|
||||
now := time.Now().Unix()
|
||||
select {
|
||||
case <-communication.HandleLiveSD:
|
||||
lastLivestreamRequest = now
|
||||
default:
|
||||
// This is the new way ;)
|
||||
if config.HubKey != "" {
|
||||
key = config.HubKey
|
||||
}
|
||||
if now-lastLivestreamRequest > 3 {
|
||||
continue
|
||||
}
|
||||
log.Log.Info("HandleLiveStreamSD: Sending base64 encoded images to MQTT.")
|
||||
sendImage(frame, topic, mqttClient, pkt, decoder, decoderMutex)
|
||||
}
|
||||
|
||||
// Cleanup the frame.
|
||||
frame.Free()
|
||||
topic := "kerberos/" + key + "/device/" + config.Key + "/live"
|
||||
|
||||
lastLivestreamRequest := int64(0)
|
||||
|
||||
var cursorError error
|
||||
var pkt av.Packet
|
||||
|
||||
for cursorError == nil {
|
||||
pkt, cursorError = livestreamCursor.ReadPacket()
|
||||
if len(pkt.Data) == 0 || !pkt.IsKeyFrame {
|
||||
continue
|
||||
}
|
||||
now := time.Now().Unix()
|
||||
select {
|
||||
case <-communication.HandleLiveSD:
|
||||
lastLivestreamRequest = now
|
||||
default:
|
||||
}
|
||||
if now-lastLivestreamRequest > 3 {
|
||||
continue
|
||||
}
|
||||
log.Log.Info("HandleLiveStreamSD: Sending base64 encoded images to MQTT.")
|
||||
sendImage(frame, topic, mqttClient, pkt, decoder, decoderMutex)
|
||||
}
|
||||
|
||||
// Cleanup the frame.
|
||||
frame.Free()
|
||||
|
||||
} else {
|
||||
log.Log.Debug("HandleLiveStreamSD: stopping as Liveview is disabled.")
|
||||
}
|
||||
}
|
||||
|
||||
log.Log.Debug("HandleLiveStreamSD: finished")
|
||||
@@ -375,34 +406,41 @@ func HandleLiveStreamHD(livestreamCursor *pubsub.QueueCursor, configuration *mod
|
||||
log.Log.Debug("HandleLiveStreamHD: stopping as Offline is enabled.")
|
||||
} else {
|
||||
|
||||
// Should create a track here.
|
||||
track := webrtc.NewVideoTrack()
|
||||
go webrtc.WriteToTrack(livestreamCursor, configuration, communication, mqttClient, track, codecs, decoder, decoderMutex)
|
||||
// Check if we need to enable the live stream
|
||||
if config.Capture.Liveview != "false" {
|
||||
|
||||
// Should create a track here.
|
||||
track := webrtc.NewVideoTrack()
|
||||
go webrtc.WriteToTrack(livestreamCursor, configuration, communication, mqttClient, track, codecs, decoder, decoderMutex)
|
||||
|
||||
if config.Capture.ForwardWebRTC == "true" {
|
||||
// We get a request with an offer, but we'll forward it.
|
||||
for m := range communication.HandleLiveHDHandshake {
|
||||
// Forward SDP
|
||||
m.CloudKey = config.Key
|
||||
request, err := json.Marshal(m)
|
||||
if err == nil {
|
||||
mqttClient.Publish("kerberos/webrtc/request", 2, false, request)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.Log.Info("HandleLiveStreamHD: Waiting for peer connections.")
|
||||
for handshake := range communication.HandleLiveHDHandshake {
|
||||
log.Log.Info("HandleLiveStreamHD: setting up a peer connection.")
|
||||
key := config.Key + "/" + handshake.Cuuid
|
||||
webrtc.CandidatesMutex.Lock()
|
||||
_, ok := webrtc.CandidateArrays[key]
|
||||
if !ok {
|
||||
webrtc.CandidateArrays[key] = make(chan string, 30)
|
||||
}
|
||||
webrtc.CandidatesMutex.Unlock()
|
||||
webrtc.InitializeWebRTCConnection(configuration, communication, mqttClient, track, handshake, webrtc.CandidateArrays[key])
|
||||
|
||||
if config.Capture.ForwardWebRTC == "true" {
|
||||
// We get a request with an offer, but we'll forward it.
|
||||
for m := range communication.HandleLiveHDHandshake {
|
||||
// Forward SDP
|
||||
m.CloudKey = config.Key
|
||||
request, err := json.Marshal(m)
|
||||
if err == nil {
|
||||
mqttClient.Publish("kerberos/webrtc/request", 2, false, request)
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
log.Log.Info("HandleLiveStreamHD: Waiting for peer connections.")
|
||||
for handshake := range communication.HandleLiveHDHandshake {
|
||||
log.Log.Info("HandleLiveStreamHD: setting up a peer connection.")
|
||||
key := config.Key + "/" + handshake.Cuuid
|
||||
webrtc.CandidatesMutex.Lock()
|
||||
_, ok := webrtc.CandidateArrays[key]
|
||||
if !ok {
|
||||
webrtc.CandidateArrays[key] = make(chan string, 30)
|
||||
}
|
||||
webrtc.CandidatesMutex.Unlock()
|
||||
webrtc.InitializeWebRTCConnection(configuration, communication, mqttClient, track, handshake, webrtc.CandidateArrays[key])
|
||||
|
||||
}
|
||||
log.Log.Debug("HandleLiveStreamHD: stopping as Liveview is disabled.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ func UploadKerberosVault(configuration *models.Configuration, fileName string, d
|
||||
config.KStorage.Directory == "" ||
|
||||
config.KStorage.URI == "" {
|
||||
log.Log.Info("UploadKerberosVault: Kerberos Vault not properly configured.")
|
||||
return false
|
||||
}
|
||||
|
||||
//fmt.Println("Uploading...")
|
||||
|
||||
@@ -44,6 +44,12 @@ func UploadS3(configuration *models.Configuration, fileName string, directory st
|
||||
aws_secret_access_key = config.HubPrivateKey
|
||||
}
|
||||
|
||||
// Check if we have some credentials otherwise we abort the request.
|
||||
if aws_access_key_id == "" || aws_secret_access_key == "" {
|
||||
log.Log.Error("UploadS3: Uploading Failed, as no credentials found")
|
||||
return false
|
||||
}
|
||||
|
||||
s3Client, err := minio.NewWithRegion("s3.amazonaws.com", aws_access_key_id, aws_secret_access_key, true, aws_region)
|
||||
if err != nil {
|
||||
log.Log.Error(err.Error())
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package components
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -42,24 +40,6 @@ func GetImageFromFilePath() (image.Image, error) {
|
||||
return nil, errors.New("Could not find a snapshot in " + snapshotDirectory)
|
||||
}
|
||||
|
||||
func GetSnapshot() string {
|
||||
var snapshot string
|
||||
files, err := ioutil.ReadDir("./data/snapshots")
|
||||
if err == nil && len(files) > 1 {
|
||||
sort.Slice(files, func(i, j int) bool {
|
||||
return files[i].ModTime().Before(files[j].ModTime())
|
||||
})
|
||||
f, _ := os.Open("./data/snapshots/" + files[1].Name())
|
||||
defer f.Close()
|
||||
// Read entire JPG into byte slice.
|
||||
reader := bufio.NewReader(f)
|
||||
content, _ := ioutil.ReadAll(reader)
|
||||
// Encode as base64.
|
||||
snapshot = base64.StdEncoding.EncodeToString(content)
|
||||
}
|
||||
return snapshot
|
||||
}
|
||||
|
||||
// ReadUserConfig Reads the user configuration of the Kerberos Open Source instance.
|
||||
// This will return a models.User struct including the username, password,
|
||||
// selected language, and if the installation was completed or not.
|
||||
@@ -146,6 +126,9 @@ func OpenConfig(configuration *models.Configuration) {
|
||||
conjungo.Merge(&s3, configuration.CustomConfig.S3, opts)
|
||||
configuration.Config.S3 = &s3
|
||||
|
||||
// Cleanup
|
||||
opts = nil
|
||||
|
||||
} else if os.Getenv("DEPLOYMENT") == "" || os.Getenv("DEPLOYMENT") == "agent" {
|
||||
|
||||
// Local deployment means we do a stand-alone installation
|
||||
@@ -244,6 +227,9 @@ func OverrideWithEnvironmentVariables(configuration *models.Configuration) {
|
||||
case "AGENT_CAPTURE_CONTINUOUS":
|
||||
configuration.Config.Capture.Continuous = value
|
||||
break
|
||||
case "AGENT_CAPTURE_LIVEVIEW":
|
||||
configuration.Config.Capture.Liveview = value
|
||||
break
|
||||
case "AGENT_CAPTURE_MOTION":
|
||||
configuration.Config.Capture.Motion = value
|
||||
break
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package components
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/kerberos-io/joy4/cgo/ffmpeg"
|
||||
|
||||
"github.com/kerberos-io/agent/machinery/src/capture"
|
||||
"github.com/kerberos-io/agent/machinery/src/cloud"
|
||||
"github.com/kerberos-io/agent/machinery/src/computervision"
|
||||
@@ -15,7 +18,6 @@ import (
|
||||
routers "github.com/kerberos-io/agent/machinery/src/routers/mqtt"
|
||||
"github.com/kerberos-io/joy4/av"
|
||||
"github.com/kerberos-io/joy4/av/pubsub"
|
||||
"github.com/kerberos-io/joy4/cgo/ffmpeg"
|
||||
"github.com/tevino/abool"
|
||||
)
|
||||
|
||||
@@ -57,12 +59,17 @@ func Bootstrap(configuration *models.Configuration, communication *models.Commun
|
||||
// do several checks to see if the agent is still operational.
|
||||
go ControlAgent(communication)
|
||||
|
||||
// Create some global variables
|
||||
decoder := &ffmpeg.VideoDecoder{}
|
||||
subDecoder := &ffmpeg.VideoDecoder{}
|
||||
cameraSettings := &models.Camera{}
|
||||
|
||||
// Run the agent and fire up all the other
|
||||
// goroutines which do image capture, motion detection, onvif, etc.
|
||||
|
||||
for {
|
||||
// This will blocking until receiving a signal to be restarted, reconfigured, stopped, etc.
|
||||
status := RunAgent(configuration, communication, uptimeStart)
|
||||
status := RunAgent(configuration, communication, uptimeStart, cameraSettings, decoder, subDecoder)
|
||||
if status == "stop" {
|
||||
break
|
||||
}
|
||||
@@ -72,7 +79,7 @@ func Bootstrap(configuration *models.Configuration, communication *models.Commun
|
||||
log.Log.Debug("Bootstrap: finished")
|
||||
}
|
||||
|
||||
func RunAgent(configuration *models.Configuration, communication *models.Communication, uptimeStart time.Time) string {
|
||||
func RunAgent(configuration *models.Configuration, communication *models.Communication, uptimeStart time.Time, cameraSettings *models.Camera, decoder *ffmpeg.VideoDecoder, subDecoder *ffmpeg.VideoDecoder) string {
|
||||
log.Log.Debug("RunAgent: started")
|
||||
|
||||
config := configuration.Config
|
||||
@@ -84,6 +91,10 @@ func RunAgent(configuration *models.Configuration, communication *models.Communi
|
||||
infile, streams, err := capture.OpenRTSP(rtspUrl)
|
||||
|
||||
var queue *pubsub.Queue
|
||||
var subQueue *pubsub.Queue
|
||||
|
||||
var decoderMutex sync.Mutex
|
||||
var subDecoderMutex sync.Mutex
|
||||
|
||||
status := "not started"
|
||||
|
||||
@@ -104,15 +115,42 @@ func RunAgent(configuration *models.Configuration, communication *models.Communi
|
||||
}
|
||||
}
|
||||
|
||||
// At some routines we will need to decode the image.
|
||||
// Make sure its properly locked as we only have a single decoder.
|
||||
var decoderMutex sync.Mutex
|
||||
var subDecoderMutex sync.Mutex
|
||||
decoder := capture.GetVideoDecoder(streams)
|
||||
// We will initialise the camera settings object
|
||||
// so we can check if the camera settings have changed, and we need
|
||||
// to reload the decoders.
|
||||
videoStream, _ := capture.GetVideoStream(streams)
|
||||
num, denum := videoStream.(av.VideoCodecData).Framerate()
|
||||
width := videoStream.(av.VideoCodecData).Width()
|
||||
height := videoStream.(av.VideoCodecData).Height()
|
||||
|
||||
var subDecoder *ffmpeg.VideoDecoder
|
||||
if subStreamEnabled {
|
||||
subDecoder = capture.GetVideoDecoder(subStreams)
|
||||
if cameraSettings.RTSP != rtspUrl || cameraSettings.SubRTSP != subRtspUrl || cameraSettings.Width != width || cameraSettings.Height != height || cameraSettings.Num != num || cameraSettings.Denum != denum || cameraSettings.Codec != videoStream.(av.VideoCodecData).Type() {
|
||||
|
||||
if cameraSettings.Initialized {
|
||||
decoder.Close()
|
||||
if subStreamEnabled {
|
||||
subDecoder.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// At some routines we will need to decode the image.
|
||||
// Make sure its properly locked as we only have a single decoder.
|
||||
log.Log.Info("RunAgent: camera settings changed, reloading decoder")
|
||||
capture.GetVideoDecoder(decoder, streams)
|
||||
if subStreamEnabled {
|
||||
capture.GetVideoDecoder(subDecoder, subStreams)
|
||||
}
|
||||
|
||||
cameraSettings.RTSP = rtspUrl
|
||||
cameraSettings.SubRTSP = subRtspUrl
|
||||
cameraSettings.Width = width
|
||||
cameraSettings.Height = height
|
||||
cameraSettings.Framerate = float64(num) / float64(denum)
|
||||
cameraSettings.Num = num
|
||||
cameraSettings.Denum = denum
|
||||
cameraSettings.Codec = videoStream.(av.VideoCodecData).Type()
|
||||
cameraSettings.Initialized = true
|
||||
} else {
|
||||
log.Log.Info("RunAgent: camera settings did not change, keeping decoder")
|
||||
}
|
||||
|
||||
communication.Decoder = decoder
|
||||
@@ -131,15 +169,15 @@ func RunAgent(configuration *models.Configuration, communication *models.Communi
|
||||
// processed by the different consumers: motion detection, recording, etc.
|
||||
queue = pubsub.NewQueue()
|
||||
communication.Queue = queue
|
||||
queue.SetMaxGopCount(int(config.Capture.PreRecording)) // GOP time frame is set to prerecording.
|
||||
log.Log.Info("RunAgent: SetMaxGopCount was set with: " + strconv.Itoa(int(config.Capture.PreRecording)))
|
||||
queue.SetMaxGopCount(int(config.Capture.PreRecording) + 1) // GOP time frame is set to prerecording (we'll add 2 gops to leave some room).
|
||||
log.Log.Info("RunAgent: SetMaxGopCount was set with: " + strconv.Itoa(int(config.Capture.PreRecording)+1))
|
||||
queue.WriteHeader(streams)
|
||||
|
||||
// We might have a substream, if so we'll create a seperate queue.
|
||||
var subQueue *pubsub.Queue
|
||||
if subStreamEnabled {
|
||||
log.Log.Info("RunAgent: Creating sub stream queue with SetMaxGopCount set to " + strconv.Itoa(int(config.Capture.PreRecording)))
|
||||
log.Log.Info("RunAgent: Creating sub stream queue with SetMaxGopCount set to " + strconv.Itoa(int(1)))
|
||||
subQueue = pubsub.NewQueue()
|
||||
communication.SubQueue = subQueue
|
||||
subQueue.SetMaxGopCount(1)
|
||||
subQueue.WriteHeader(subStreams)
|
||||
}
|
||||
@@ -214,22 +252,30 @@ func RunAgent(configuration *models.Configuration, communication *models.Communi
|
||||
time.Sleep(time.Second * 1)
|
||||
|
||||
infile.Close()
|
||||
infile = nil
|
||||
queue.Close()
|
||||
queue = nil
|
||||
communication.Queue = nil
|
||||
if subStreamEnabled {
|
||||
subInfile.Close()
|
||||
subInfile = nil
|
||||
subQueue.Close()
|
||||
subQueue = nil
|
||||
communication.SubQueue = nil
|
||||
}
|
||||
close(communication.HandleONVIF)
|
||||
communication.HandleONVIF = nil
|
||||
close(communication.HandleLiveHDHandshake)
|
||||
communication.HandleLiveHDHandshake = nil
|
||||
close(communication.HandleMotion)
|
||||
routers.DisconnectMQTT(mqttClient)
|
||||
communication.HandleMotion = nil
|
||||
|
||||
// Disconnect MQTT
|
||||
routers.DisconnectMQTT(mqttClient, &configuration.Config)
|
||||
|
||||
// Wait a few seconds to stop the decoder.
|
||||
time.Sleep(time.Second * 3)
|
||||
decoder.Close()
|
||||
if subStreamEnabled {
|
||||
subDecoder.Close()
|
||||
}
|
||||
|
||||
// Waiting for some seconds to make sure everything is properly closed.
|
||||
log.Log.Info("RunAgent: waiting 3 seconds to make sure everything is properly closed.")
|
||||
time.Sleep(time.Second * 3)
|
||||
@@ -240,6 +286,9 @@ func RunAgent(configuration *models.Configuration, communication *models.Communi
|
||||
|
||||
log.Log.Debug("RunAgent: finished")
|
||||
|
||||
// Clean up, force garbage collection
|
||||
runtime.GC()
|
||||
|
||||
return status
|
||||
}
|
||||
|
||||
|
||||
@@ -3,12 +3,9 @@ package computervision
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"image"
|
||||
"image/jpeg"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -130,26 +127,7 @@ func ProcessMotion(motionCursor *pubsub.QueueCursor, configuration *models.Confi
|
||||
|
||||
// Store snapshots (jpg) for hull.
|
||||
if config.Capture.Snapshots != "false" {
|
||||
files, err := ioutil.ReadDir("./data/snapshots")
|
||||
if err == nil {
|
||||
rgbImage, err := GetRawImage(frame, pkt, decoder, decoderMutex)
|
||||
if err == nil {
|
||||
sort.Slice(files, func(i, j int) bool {
|
||||
return files[i].ModTime().Before(files[j].ModTime())
|
||||
})
|
||||
if len(files) > 3 {
|
||||
os.Remove("./data/snapshots/" + files[0].Name())
|
||||
}
|
||||
|
||||
// Save image
|
||||
t := strconv.FormatInt(time.Now().Unix(), 10)
|
||||
f, err := os.Create("./data/snapshots/" + t + ".jpg")
|
||||
if err == nil {
|
||||
jpeg.Encode(f, &rgbImage.Image, &jpeg.Options{Quality: 15})
|
||||
f.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
StoreSnapshot(communication, frame, pkt, decoder, decoderMutex)
|
||||
}
|
||||
|
||||
// Check if within time interval
|
||||
@@ -255,3 +233,16 @@ func AbsDiffBitwiseAndThreshold(img1 *image.Gray, img2 *image.Gray, img3 *image.
|
||||
}
|
||||
return changes
|
||||
}
|
||||
|
||||
func StoreSnapshot(communication *models.Communication, frame *ffmpeg.VideoFrame, pkt av.Packet, decoder *ffmpeg.VideoDecoder, decoderMutex *sync.Mutex) {
|
||||
rgbImage, err := GetRawImage(frame, pkt, decoder, decoderMutex)
|
||||
if err == nil {
|
||||
buffer := new(bytes.Buffer)
|
||||
w := bufio.NewWriter(buffer)
|
||||
err := jpeg.Encode(w, &rgbImage.Image, &jpeg.Options{Quality: 15})
|
||||
if err == nil {
|
||||
snapshot := base64.StdEncoding.EncodeToString(buffer.Bytes())
|
||||
communication.Image = snapshot
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
15
machinery/src/models/Camera.go
Normal file
15
machinery/src/models/Camera.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package models
|
||||
|
||||
import "github.com/kerberos-io/joy4/av"
|
||||
|
||||
type Camera struct {
|
||||
Width int
|
||||
Height int
|
||||
Num int
|
||||
Denum int
|
||||
Framerate float64
|
||||
RTSP string
|
||||
SubRTSP string
|
||||
Codec av.CodecType
|
||||
Initialized bool
|
||||
}
|
||||
@@ -28,8 +28,10 @@ type Communication struct {
|
||||
HandleONVIF chan OnvifAction
|
||||
IsConfiguring *abool.AtomicBool
|
||||
Queue *pubsub.Queue
|
||||
SubQueue *pubsub.Queue
|
||||
DecoderMutex *sync.Mutex
|
||||
SubDecoderMutex *sync.Mutex
|
||||
Decoder *ffmpeg.VideoDecoder
|
||||
SubDecoder *ffmpeg.VideoDecoder
|
||||
Image string
|
||||
}
|
||||
|
||||
@@ -50,9 +50,10 @@ type Capture struct {
|
||||
USBCamera USBCamera `json:"usbcamera"`
|
||||
RaspiCamera RaspiCamera `json:"raspicamera"`
|
||||
Recording string `json:"recording,omitempty"`
|
||||
Continuous string `json:"continuous,omitempty"`
|
||||
Snapshots string `json:"snapshots,omitempty"`
|
||||
Motion string `json:"motion,omitempty"`
|
||||
Liveview string `json:"liveview,omitempty"`
|
||||
Continuous string `json:"continuous,omitempty"`
|
||||
PostRecording int64 `json:"postrecording"`
|
||||
PreRecording int64 `json:"prerecording"`
|
||||
MaxLengthRecording int64 `json:"maxlengthrecording"`
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
package models
|
||||
|
||||
type System struct {
|
||||
CPUId string `json:"cpu_idle" bson:"cpu_idle"`
|
||||
Hostname string `json:"hostname" bson:"hostname"`
|
||||
Version string `json:"version" bson:"version"`
|
||||
Release string `json:"release" bson:"release"`
|
||||
BootTime uint64 `json:"boot_time" bson:"boot_time"`
|
||||
KernelVersion string `json:"kernel_version" bson:"kernel_version"`
|
||||
MACs []string `json:"macs" bson:"macs"`
|
||||
IPs []string `json:"ips" bson:"ips"`
|
||||
Architecture string `json:"architecture" bson:"architecture"`
|
||||
UsedMemory uint64 `json:"used_memory" bson:"used_memory"`
|
||||
TotalMemory uint64 `json:"total_memory" bson:"total_memory"`
|
||||
FreeMemory uint64 `json:"free_memory" bson:"free_memory"`
|
||||
CPUId string `json:"cpu_idle" bson:"cpu_idle"`
|
||||
Hostname string `json:"hostname" bson:"hostname"`
|
||||
Version string `json:"version" bson:"version"`
|
||||
Release string `json:"release" bson:"release"`
|
||||
BootTime uint64 `json:"boot_time" bson:"boot_time"`
|
||||
KernelVersion string `json:"kernel_version" bson:"kernel_version"`
|
||||
MACs []string `json:"macs" bson:"macs"`
|
||||
IPs []string `json:"ips" bson:"ips"`
|
||||
Architecture string `json:"architecture" bson:"architecture"`
|
||||
UsedMemory uint64 `json:"used_memory" bson:"used_memory"`
|
||||
TotalMemory uint64 `json:"total_memory" bson:"total_memory"`
|
||||
FreeMemory uint64 `json:"free_memory" bson:"free_memory"`
|
||||
ProcessUsedMemory uint64 `json:"process_used_memory" bson:"process_used_memory"`
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ func AddRoutes(r *gin.Engine, authMiddleware *jwt.GinJWTMiddleware, configuratio
|
||||
"config": configuration.Config,
|
||||
"custom": configuration.CustomConfig,
|
||||
"global": configuration.GlobalConfig,
|
||||
"snapshot": components.GetSnapshot(),
|
||||
"snapshot": communication.Image,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -157,7 +157,7 @@ func AddRoutes(r *gin.Engine, authMiddleware *jwt.GinJWTMiddleware, configuratio
|
||||
"config": configuration.Config,
|
||||
"custom": configuration.CustomConfig,
|
||||
"global": configuration.GlobalConfig,
|
||||
"snapshot": components.GetSnapshot(),
|
||||
"snapshot": communication.Image,
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -193,8 +193,16 @@ func MQTTListenerHandleONVIF(mqttClient mqtt.Client, hubKey string, configuratio
|
||||
})
|
||||
}
|
||||
|
||||
func DisconnectMQTT(mqttClient mqtt.Client) {
|
||||
func DisconnectMQTT(mqttClient mqtt.Client, config *models.Config) {
|
||||
if mqttClient != nil {
|
||||
// Cleanup all subscriptions.
|
||||
mqttClient.Unsubscribe("kerberos/" + config.HubKey + "/device/" + config.Key + "/request-live")
|
||||
mqttClient.Unsubscribe(config.Key + "/register")
|
||||
mqttClient.Unsubscribe("kerberos/webrtc/keepalivehub/" + config.Key)
|
||||
mqttClient.Unsubscribe("kerberos/webrtc/peers/" + config.Key)
|
||||
mqttClient.Unsubscribe("candidate/cloud")
|
||||
mqttClient.Unsubscribe("kerberos/onvif/" + config.Key)
|
||||
mqttClient.Disconnect(1000)
|
||||
mqttClient = nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@ func ForwardSDStream(ctx context.Context, clientID string, connection *Connectio
|
||||
logreader:
|
||||
for {
|
||||
var encodedImage string
|
||||
if cursor != nil && decoder != nil {
|
||||
if queue != nil && cursor != nil && decoder != nil {
|
||||
pkt, err := cursor.ReadPacket()
|
||||
if err == nil {
|
||||
if !pkt.IsKeyFrame {
|
||||
|
||||
@@ -271,108 +271,3 @@ func CreateFragmentedMP4(fullName string, fragmentedDuration int64) {
|
||||
os.Remove(fullName)
|
||||
os.Rename(fullName+"f.mp4", fullName)
|
||||
}
|
||||
|
||||
/*func FloatToString(input_num float64) string {
|
||||
// to convert a float number to a string
|
||||
return strconv.FormatFloat(input_num, 'f', 6, 64)
|
||||
}
|
||||
|
||||
func ParseFMP4(file *bytes.Reader) (error, uint64, uint64, []*mp4.MediaSegment, uint64) {
|
||||
var ftypSize, moovSize uint64
|
||||
var segments []*mp4.MediaSegment
|
||||
var duration uint64
|
||||
parsedMp4, err := mp4.DecodeFile(file, mp4.WithDecodeMode(mp4.DecModeLazyMdat))
|
||||
if parsedMp4 != nil && parsedMp4.Init != nil && err == nil {
|
||||
ftypSize = parsedMp4.Init.Ftyp.Size()
|
||||
moovSize = parsedMp4.Init.Moov.Size()
|
||||
duration = parsedMp4.Moov.Trak.Tkhd.Duration
|
||||
segments = parsedMp4.Segments
|
||||
}
|
||||
return err, ftypSize, moovSize, segments, duration
|
||||
}
|
||||
|
||||
func ParseFMP4Detail(fullName string) {
|
||||
|
||||
// open fullName
|
||||
file, err := os.Open(fullName)
|
||||
if err != nil {
|
||||
log.Log.Error("Error opening file: " + err.Error())
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
fileBytes, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
log.Log.Error("Error reading file: " + err.Error())
|
||||
}
|
||||
fileReader := bytes.NewReader(fileBytes)
|
||||
|
||||
err, ftypSize, moovSize, segments, dur := ParseFMP4(fileReader)
|
||||
fmt.Println("========== Fragmented details =================")
|
||||
fmt.Println(dur)
|
||||
fmt.Println(ftypSize)
|
||||
fmt.Println(moovSize)
|
||||
fmt.Println(len(segments))
|
||||
|
||||
// Calculate duration of segments
|
||||
var totalDuration uint64
|
||||
for _, segment := range segments {
|
||||
fragments := segment.Fragments
|
||||
|
||||
for _, fragment := range fragments {
|
||||
var sampleDuration uint32
|
||||
samples := fragment.Moof.Traf.Trun.Samples
|
||||
for _, sample := range samples {
|
||||
sampleDuration += sample.Dur
|
||||
}
|
||||
fmt.Println(sampleDuration)
|
||||
totalDuration += uint64(sampleDuration)
|
||||
}
|
||||
}
|
||||
|
||||
misingDuration := dur - totalDuration/100
|
||||
fmt.Println(misingDuration)
|
||||
fmt.Println(totalDuration/100 + misingDuration)
|
||||
}
|
||||
|
||||
func CreateFragmentByteRanges(log log.Logging, fileName string, ftypSize uint64, moovSize uint64, segments []*mp4.MediaSegment) (string, []models.BytesRangeOnTime) {
|
||||
size := strconv.FormatInt(int64(ftypSize+moovSize), 10)
|
||||
|
||||
var fileFragments strings.Builder
|
||||
fileFragments.WriteString("#EXT-X-MAP:URI=\"" + fileName + "\",BYTERANGE=\"" + size + "@0\"\n")
|
||||
|
||||
var bytesRangeOnTime []models.BytesRangeOnTime
|
||||
var currentTime uint32
|
||||
for _, segment := range segments {
|
||||
fragments := segment.Fragments
|
||||
for _, fragment := range fragments {
|
||||
var sampleDuration uint32
|
||||
samples := fragment.Moof.Traf.Trun.Samples
|
||||
for _, sample := range samples {
|
||||
sampleDuration += sample.Dur
|
||||
}
|
||||
currentTime = currentTime + sampleDuration
|
||||
duration := FloatToString(float64(sampleDuration / 100000))
|
||||
|
||||
startPos := fragment.Moof.StartPos
|
||||
start := strconv.FormatInt(int64(startPos), 10)
|
||||
|
||||
totalSize := fragment.Mdat.Size() + fragment.Moof.Size()
|
||||
size := strconv.FormatInt(int64(totalSize), 10)
|
||||
|
||||
fileFragments.WriteString("#EXTINF:" + duration + ",\n") // @TODO calculate the duration
|
||||
fileFragments.WriteString("#EXT-X-BYTERANGE:" + size + "@" + start + "\n")
|
||||
fileFragments.WriteString(fileName + "\n")
|
||||
|
||||
byteRange := models.BytesRangeOnTime{
|
||||
Duration: duration,
|
||||
Time: FloatToString(float64(currentTime) / 100000),
|
||||
Range: size + "@" + start,
|
||||
}
|
||||
bytesRangeOnTime = append(bytesRangeOnTime, byteRange)
|
||||
}
|
||||
}
|
||||
|
||||
bytesRanges := fileFragments.String()
|
||||
log.Info("Fragments calculate from " + fileName + ": " + bytesRanges)
|
||||
return bytesRanges, bytesRangeOnTime
|
||||
}*/
|
||||
|
||||
204
ui/public/locales/ja/translation.json
Normal file
204
ui/public/locales/ja/translation.json
Normal file
@@ -0,0 +1,204 @@
|
||||
{
|
||||
"breadcrumb": {
|
||||
"watch_recordings": "録画を見る",
|
||||
"configure": "設定"
|
||||
},
|
||||
"buttons": {
|
||||
"save": "保存"
|
||||
},
|
||||
"navigation": {
|
||||
"profile": "プロフィール",
|
||||
"admin": "管理者",
|
||||
"management": "管理",
|
||||
"dashboard": "ダッシュボード",
|
||||
"recordings": "録画",
|
||||
"settings": "設定",
|
||||
"help_support": "ヘルプ",
|
||||
"swagger": "Swagger API",
|
||||
"documentation": "ドキュメンテーション",
|
||||
"ui_library": "UI ライブラリ",
|
||||
"layout": "言語",
|
||||
"choose_language": "言語を選択"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "ダッシュボード",
|
||||
"heading": "ビデオ監視の概要",
|
||||
"number_of_days": "日数",
|
||||
"total_recordings": "録画一覧",
|
||||
"connected": "接続済み",
|
||||
"not_connected": "接続されていません",
|
||||
"offline_mode": "オフラインモード",
|
||||
"latest_events": "最新のイベント",
|
||||
"configure_connection": "接続の構成",
|
||||
"no_events": "イベントなし",
|
||||
"no_events_description": "記録が見つかりません。Kerberos エージェントが正しく構成されていることを確認してください。",
|
||||
"motion_detected": "モーションが検出されました",
|
||||
"live_view": "ライブビュー",
|
||||
"loading_live_view": "ライブビューを読み込んでいます",
|
||||
"loading_live_view_description": "ライブ ビューをロードしています。お待ちください。",
|
||||
"time": "時間",
|
||||
"description": "説明",
|
||||
"name": "名前"
|
||||
},
|
||||
"recordings": {
|
||||
"title": "録画",
|
||||
"heading": "すべての録音を 1 か所に",
|
||||
"search_media": "メディアを検索"
|
||||
},
|
||||
"settings": {
|
||||
"title": "設定",
|
||||
"heading": "搭載カメラ",
|
||||
"submenu": {
|
||||
"all": "全て",
|
||||
"overview": "概要",
|
||||
"camera": "カメラ",
|
||||
"recording": "録音",
|
||||
"streaming": "ストリーミング",
|
||||
"conditions": "条件",
|
||||
"persistence": "持続性"
|
||||
},
|
||||
"info": {
|
||||
"kerberos_hub_demo": "Kerberos Hub のデモ環境を見て、Kerberos Hub の動作を確認してください。",
|
||||
"configuration_updated_success": "構成が正常に更新されました。",
|
||||
"configuration_updated_error": "保存中にエラーが発生しました。",
|
||||
"verify_hub": "Kerberos Hub の設定を確認しています。",
|
||||
"verify_hub_success": "Kerberos Hub 設定が正常に検証されました。",
|
||||
"verify_hub_error": "Kerberos Hub の検証中に問題が発生しました",
|
||||
"verify_persistence": "持続性設定を確認しています。",
|
||||
"verify_persistence_success": "持続性設定が正常に検証されました。",
|
||||
"verify_persistence_error": "持続性の検証中に問題が発生しました",
|
||||
"verify_camera": "カメラの設定を確認しています。",
|
||||
"verify_camera_success": "カメラの設定が正常に検証されました。",
|
||||
"verify_camera_error": "カメラ設定の確認中に問題が発生しました"
|
||||
},
|
||||
"overview": {
|
||||
"general": "全般的",
|
||||
"description_general": "Kerberos エージェントの一般設定",
|
||||
"key": "鍵",
|
||||
"camera_name": "カメラ名",
|
||||
"timezone": "タイムゾーン",
|
||||
"select_timezone": "タイムゾーンを選択",
|
||||
"advanced_configuration": "詳細設定",
|
||||
"description_advanced_configuration": "Kerberos エージェントの特定の部分を有効または無効にするための詳細な構成オプション",
|
||||
"offline_mode": "オフラインモード",
|
||||
"description_offline_mode": "すべての送信トラフィックを無効にする"
|
||||
},
|
||||
"camera": {
|
||||
"camera": "カメラ",
|
||||
"description_camera": "選択したカメラに接続するには、カメラの設定が必要です。",
|
||||
"only_h264": "現在、H264 RTSP ストリームのみがサポートされています。",
|
||||
"rtsp_url": "RTSP URL",
|
||||
"rtsp_h264": "カメラへの H264 RTSP 接続。",
|
||||
"sub_rtsp_url": "Sub RTSP url (ライブストリーミングに使用)",
|
||||
"sub_rtsp_h264": "カメラの低解像度へのセカンダリ RTSP 接続。",
|
||||
"onvif": "ONVIF",
|
||||
"description_onvif": "ONVIF 機能と通信するための資格情報",
|
||||
"onvif_xaddr": "ONVIF xaddr",
|
||||
"onvif_username": "ONVIF ユーザー名",
|
||||
"onvif_password": "ONVIF パスワード",
|
||||
"verify_connection": "接続の確認",
|
||||
"verify_sub_connection": "サブ接続の確認"
|
||||
},
|
||||
"recording": {
|
||||
"recording": "録画",
|
||||
"description_recording": "録画方法を指定します。",
|
||||
"continuous_recording": "連続記録",
|
||||
"description_continuous_recording": "24 時間またはモーション ベースの録画を行います。",
|
||||
"max_duration": "動画の最大再生時間 (秒)",
|
||||
"description_max_duration": "録音の最大継続時間。",
|
||||
"pre_recording": "事前録画 (キー フレームのバッファリング)",
|
||||
"description_pre_recording": "イベントが発生する数秒前。",
|
||||
"post_recording": "ポストレコーディング (秒)",
|
||||
"description_post_recording": "イベントが発生してからの秒数。",
|
||||
"threshold": "記録閾値(ピクセル)",
|
||||
"description_threshold": "記録するために変更されたピクセル数",
|
||||
"autoclean": "オートクリーン",
|
||||
"description_autoclean": "特定のストレージ容量 (MB) に達したときに、Kerberos エージェントが記録をクリーンアップできるかどうかを指定します。",
|
||||
"autoclean_enable": "自動クリーニングを有効にする",
|
||||
"autoclean_description_enable": "容量に達したら、最も古い記録を削除します。",
|
||||
"autoclean_max_directory_size": "最大ディレクトリ サイズ (MB)",
|
||||
"autoclean_description_max_directory_size": "保存された録音の最大 MB。",
|
||||
"fragmentedrecordings": "断片化された録音",
|
||||
"description_fragmentedrecordings": "録音が断片化されている場合、HLS ストリームに適しています。",
|
||||
"fragmentedrecordings_enable": "断片化を有効にする",
|
||||
"fragmentedrecordings_description_enable": "HLS には断片化された録音が必要です。",
|
||||
"fragmentedrecordings_duration": "フラグメント期間",
|
||||
"fragmentedrecordings_description_duration": "1 つのフラグメントの持続時間。"
|
||||
},
|
||||
"streaming": {
|
||||
"stun_turn": "WebRTCのSTUN/TURN",
|
||||
"description_stun_turn": "フル解像度のライブ ストリーミングには、WebRTC の概念を使用します。",
|
||||
"stun_server": "STUNサーバー",
|
||||
"turn_server": "TURNサーバー",
|
||||
"turn_username": "ユーザー名",
|
||||
"turn_password": "パスワード",
|
||||
"stun_turn_forward": "転送とトランスコーディング",
|
||||
"stun_turn_description_forward": "TURN/STUN 通信の最適化と機能強化。",
|
||||
"stun_turn_webrtc": "WebRTC ブローカーへの転送",
|
||||
"stun_turn_description_webrtc": "MQTT を介して h264 ストリームを転送する",
|
||||
"stun_turn_transcode": "トランスコード ストリーム",
|
||||
"stun_turn_description_transcode": "ストリームを低解像度に変換する",
|
||||
"stun_turn_downscale": "解像度のダウンスケール (% または元の解像度)",
|
||||
"mqtt": "MQTT",
|
||||
"description_mqtt": "それらの通信にはMQTT ブローカーが使用されます。",
|
||||
"description2_mqtt": "たとえば、ライブストリーミングや ONVIF (PTZ) 機能を実現するために、Kerberos エージェントに送信します。",
|
||||
"mqtt_brokeruri": "ブローカー URI",
|
||||
"mqtt_username": "ユーザー名",
|
||||
"mqtt_password": "パスワード"
|
||||
},
|
||||
"conditions": {
|
||||
"timeofinterest": "特定の時間",
|
||||
"description_timeofinterest": "特定の時間間隔 (タイムゾーンに基づく) の間のみ録画を行います。",
|
||||
"timeofinterest_enabled": "有効",
|
||||
"timeofinterest_description_enabled": "有効にすると、時間枠を指定できます",
|
||||
"sunday": "日曜日",
|
||||
"monday": "月曜日",
|
||||
"tuesday": "火曜日",
|
||||
"wednesday": "水曜日",
|
||||
"thursday": "木曜日",
|
||||
"friday": "金曜日",
|
||||
"saturday": "土曜日",
|
||||
"externalcondition": "外部条件",
|
||||
"description_externalcondition": "外部 Web サービスに応じて、記録を有効または無効にすることができます。",
|
||||
"regionofinterest": "検出領域",
|
||||
"description_regionofinterest": "1 つまたは複数の領域を定義すると、定義した領域でのみモーションが追跡されます。"
|
||||
},
|
||||
"persistence": {
|
||||
"kerberoshub": "ケルベロス ハブ",
|
||||
"description_kerberoshub": "Kerberos エージェントはハートビートを中央に送信できます。",
|
||||
"description2_kerberoshub": "インストール。",
|
||||
"persistence": "持続性",
|
||||
"saasoffering": "Kerberos ハブ (SAAS オファリング)",
|
||||
"description_persistence": "録音を保存する機能を持つことは、すべての始まりです。",
|
||||
"description2_persistence": "、またはサードパーティのプロバイダ",
|
||||
"select_persistence": "永続性を選択",
|
||||
"kerberoshub_proxyurl": "Kerberos ハブ プロキシ URL",
|
||||
"kerberoshub_description_proxyurl": "記録をアップロードするためのプロキシ エンドポイント。",
|
||||
"kerberoshub_apiurl": "ケルベロス ハブ API URL",
|
||||
"kerberoshub_description_apiurl": "録音をアップロードするための API エンドポイント。",
|
||||
"kerberoshub_publickey": "公開鍵",
|
||||
"kerberoshub_description_publickey": "Kerberos Hub アカウントに付与された公開鍵。",
|
||||
"kerberoshub_privatekey": "秘密鍵",
|
||||
"kerberoshub_description_privatekey": "Kerberos Hub アカウントに付与された秘密鍵。",
|
||||
"kerberoshub_site": "サイト",
|
||||
"kerberoshub_description_site": "Kerberos Hub で Kerberos エージェントが属しているサイト ID。",
|
||||
"kerberoshub_region": "領域",
|
||||
"kerberoshub_description_region": "録音を保存しているリージョン。",
|
||||
"kerberoshub_bucket": "bucket",
|
||||
"kerberoshub_description_bucket": "録音を保存しているbucket",
|
||||
"kerberoshub_username": "ユーザー名/ディレクトリ",
|
||||
"kerberoshub_description_username": "Kerberos Hub アカウントのユーザー名。",
|
||||
"kerberosvault_apiurl": "Kerberos ボールト API URL",
|
||||
"kerberosvault_description_apiurl": "Kerberos ボールト API",
|
||||
"kerberosvault_provider": "プロバイダ",
|
||||
"kerberosvault_description_provider": "録音の送信先のプロバイダー。",
|
||||
"kerberosvault_directory": "ディレクトリ",
|
||||
"kerberosvault_description_directory": "録音がプロバイダーに保存されるサブディレクトリ。",
|
||||
"kerberosvault_accesskey": "アクセスキー",
|
||||
"kerberosvault_description_accesskey": "Kerberos Vault アカウントのアクセス キー。",
|
||||
"kerberosvault_secretkey": "秘密鍵",
|
||||
"kerberosvault_description_secretkey": "Kerberos Vault アカウントの秘密鍵。",
|
||||
"verify_connection": "接続の確認"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"breadcrumb": {
|
||||
"watch_recordings": "Watch recordings",
|
||||
"configure": "Configure"
|
||||
"watch_recordings": "Assistir gravações",
|
||||
"configure": "Configurar"
|
||||
},
|
||||
"buttons": {
|
||||
"save": "Save"
|
||||
"save": "Salvar"
|
||||
},
|
||||
"navigation": {
|
||||
"profile": "Perfil",
|
||||
@@ -12,7 +12,7 @@
|
||||
"management": "Gestão",
|
||||
"dashboard": "Painel",
|
||||
"recordings": "Gravações",
|
||||
"settings": "Definições",
|
||||
"settings": "Configurações",
|
||||
"help_support": "Ajuda e suporte",
|
||||
"swagger": "Swagger API",
|
||||
"documentation": "Documentação",
|
||||
@@ -21,184 +21,184 @@
|
||||
"choose_language": "Escolha o seu idioma"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Dashboard",
|
||||
"heading": "Overview of your video surveilance",
|
||||
"number_of_days": "Number of days",
|
||||
"total_recordings": "Total recordings",
|
||||
"connected": "Connected",
|
||||
"not_connected": "Not connected",
|
||||
"offline_mode": "Offline mode",
|
||||
"latest_events": "Latest events",
|
||||
"configure_connection": "Configure connection",
|
||||
"no_events": "No events",
|
||||
"no_events_description": "No recordings where found, make sure your Kerberos Agent is properly configured.",
|
||||
"motion_detected": "Motion was detected",
|
||||
"live_view": "Live view",
|
||||
"loading_live_view": "Loading live view",
|
||||
"loading_live_view_description": "Hold on we are loading your live view here. If you didn't configure your camera connection, update it on the settings pages.",
|
||||
"title": "Painel",
|
||||
"heading": "Visão geral de sua videovigilância",
|
||||
"number_of_days": "Número de dias",
|
||||
"total_recordings": "Total de gravações",
|
||||
"connected": "Conectado",
|
||||
"not_connected": "Desconectado",
|
||||
"offline_mode": "Modo Offline",
|
||||
"latest_events": "Ultimos eventos",
|
||||
"configure_connection": "Configurar conexão",
|
||||
"no_events": "Nenhum evento",
|
||||
"no_events_description": "Nenhuma gravação foi encontrada, certifique-se de que seu Kerberos Agent esteja configurado corretamente.",
|
||||
"motion_detected": "Movimento detectado",
|
||||
"live_view": "Visualização ao vivo",
|
||||
"loading_live_view": "Carregando visualização ao vivo",
|
||||
"loading_live_view_description": "Aguarde, estamos carregando sua visualização ao vivo. Se você não configurou a conexão de sua câmera, atualize-a nas páginas de configurações.",
|
||||
"time": "Time",
|
||||
"description": "Description",
|
||||
"name": "Name"
|
||||
"description": "Descrição",
|
||||
"name": "Nome"
|
||||
},
|
||||
"recordings": {
|
||||
"title": "Recordings",
|
||||
"heading": "All your recordings in a single place",
|
||||
"search_media": "Search media"
|
||||
"title": "Gravações",
|
||||
"heading": "Todas as suas gravações em um único lugar",
|
||||
"search_media": "Pesquisar mídia"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Settings",
|
||||
"heading": "Onboard your camera",
|
||||
"title": "Configurações",
|
||||
"heading": "Integre sua câmera",
|
||||
"submenu": {
|
||||
"all": "All",
|
||||
"overview": "Overview",
|
||||
"camera": "Camera",
|
||||
"recording": "Recording",
|
||||
"streaming": "Streaming",
|
||||
"conditions": "Conditions",
|
||||
"persistence": "Persistence"
|
||||
"all": "Todas",
|
||||
"overview": "Visão geral",
|
||||
"camera": "Câmera",
|
||||
"recording": "Gravação",
|
||||
"streaming": "Transmissão",
|
||||
"conditions": "Condições",
|
||||
"persistence": "Armazenamento"
|
||||
},
|
||||
"info": {
|
||||
"kerberos_hub_demo": "Have a look at our Kerberos Hub demo environment, to see Kerberos Hub in action!",
|
||||
"configuration_updated_success": "Your configuration have been updated successfully.",
|
||||
"configuration_updated_error": "Something went wrong while saving.",
|
||||
"verify_hub": "Verifying your Kerberos Hub settings.",
|
||||
"verify_hub_success": "Kerberos Hub settings are successfully verified.",
|
||||
"verify_hub_error": "Something went wrong while verifying Kerberos Hub",
|
||||
"verify_persistence": "Verifying your persistence settings.",
|
||||
"verify_persistence_success": "Persistence settings are successfully verified.",
|
||||
"verify_persistence_error": "Something went wrong while verifying the persistence",
|
||||
"verify_camera": "Verifying your camera settings.",
|
||||
"verify_camera_success": "Camera settings are successfully verified.",
|
||||
"verify_camera_error": "Something went wrong while verifying the camera settings"
|
||||
"kerberos_hub_demo": "Dê uma olhada em nosso ambiente de demonstração do Kerberos Hub para ver o Kerberos Hub em ação!",
|
||||
"configuration_updated_success": "Sua configuração foi atualizada com sucesso.",
|
||||
"configuration_updated_error": "Ocorreu um erro durante o salvamento.",
|
||||
"verify_hub": "Verificando as configurações do Kerberos Hub.",
|
||||
"verify_hub_success": "As configurações do Kerberos Hub foram verificadas com sucesso.",
|
||||
"verify_hub_error": "Algo deu errado ao verificar o Kerberos Hub",
|
||||
"verify_persistence": "Verificando suas configurações de armazenamento.",
|
||||
"verify_persistence_success": "As configurações de armazenamento foram verificadas com sucesso.",
|
||||
"verify_persistence_error": "Algo deu errado ao verificar o armazenamento",
|
||||
"verify_camera": "Verificando as configurações de sua câmera.",
|
||||
"verify_camera_success": "As configurações da câmera foram verificadas com sucesso.",
|
||||
"verify_camera_error": "Algo deu errado ao verificar as configurações da câmera."
|
||||
},
|
||||
"overview": {
|
||||
"general": "General",
|
||||
"description_general": "General settings for your Kerberos Agent",
|
||||
"key": "Key",
|
||||
"camera_name": "Camera name",
|
||||
"timezone": "Timezone",
|
||||
"select_timezone": "Select a timezone",
|
||||
"advanced_configuration": "Advanced configuration",
|
||||
"description_advanced_configuration": "Detailed configuration options to enable or disable specific parts of the Kerberos Agent",
|
||||
"offline_mode": "Offline mode",
|
||||
"description_offline_mode": "Disable all outgoing traffic"
|
||||
"general": "Geral",
|
||||
"description_general": "Configurações gerais para seu agente Kerberos",
|
||||
"key": "Chave",
|
||||
"camera_name": "Nome da câmera",
|
||||
"timezone": "Fuso horário",
|
||||
"select_timezone": "Selecione a timezone",
|
||||
"advanced_configuration": "Configurações avançadas",
|
||||
"description_advanced_configuration": "Opções de configuração detalhadas para habilitar ou desabilitar partes específicas do Kerberos Agent",
|
||||
"offline_mode": "Modo Offline",
|
||||
"description_offline_mode": "Desative todo o tráfego de saída"
|
||||
},
|
||||
"camera": {
|
||||
"camera": "Camera",
|
||||
"description_camera": "Camera settings are required to make a connection to your camera of choice.",
|
||||
"only_h264": "Currently only H264 RTSP streams are supported.",
|
||||
"rtsp_url": "RTSP url",
|
||||
"rtsp_h264": "A H264 RTSP connection to your camera.",
|
||||
"sub_rtsp_url": "Sub RTSP url (used for livestreaming)",
|
||||
"sub_rtsp_h264": "A secondary RTSP connection to the low resolution of your camera.",
|
||||
"camera": "Câmera",
|
||||
"description_camera": "As configurações da câmera são necessárias para fazer uma conexão com a câmera de sua escolha.",
|
||||
"only_h264": "Atualmente, apenas streams H264 RTSP são suportados.",
|
||||
"rtsp_url": "Url RTSP",
|
||||
"rtsp_h264": "Uma conexão H264 RTSP para sua câmera.",
|
||||
"sub_rtsp_url": "Sub RTSP URL(usado para transmissão ao vivo)",
|
||||
"sub_rtsp_h264": "Uma conexão RTSP secundária para a baixa resolução de sua câmera.",
|
||||
"onvif": "ONVIF",
|
||||
"description_onvif": "Credentials to communicate with ONVIF capabilities. These are used for PTZ or other capabilities provided by the camera.",
|
||||
"description_onvif": "Credenciais para se comunicar com recursos ONVIF. Eles são usados para PTZ ou outros recursos fornecidos pela câmera.",
|
||||
"onvif_xaddr": "ONVIF xaddr",
|
||||
"onvif_username": "ONVIF username",
|
||||
"onvif_password": "ONVIF password",
|
||||
"verify_connection": "Verify Connection",
|
||||
"verify_sub_connection": "Verify Sub Connection"
|
||||
"onvif_username": "ONVIF usuario (username)",
|
||||
"onvif_password": "ONVIF senha (password)",
|
||||
"verify_connection": "Verificar conexão",
|
||||
"verify_sub_connection": "Verificar subconexão"
|
||||
},
|
||||
"recording": {
|
||||
"recording": "Recording",
|
||||
"description_recording": "Specify how you would like to make recordings. Having a continuous 24/7 setup or a motion based recording.",
|
||||
"continuous_recording": "Continuous recording",
|
||||
"description_continuous_recording": "Make 24/7 or motion based recordings.",
|
||||
"max_duration": "max video duration (seconds)",
|
||||
"description_max_duration": "The maximum duration of a recording.",
|
||||
"pre_recording": "pre recording (key frames buffered)",
|
||||
"description_pre_recording": "Seconds before an event occurred.",
|
||||
"post_recording": "post recording (seconds)",
|
||||
"description_post_recording": "Seconds after an event occurred.",
|
||||
"threshold": "Recording threshold (pixels)",
|
||||
"description_threshold": "The number of pixels changed to record",
|
||||
"autoclean": "Auto clean",
|
||||
"description_autoclean": "Specify if the Kerberos Agent can cleanup recordings when a specific storage capacity (MB) is reached. This will remove the oldest recordings when the capacity is reached.",
|
||||
"autoclean_enable": "Enable auto clean",
|
||||
"autoclean_description_enable": "Remove oldest recording when capacity reached.",
|
||||
"autoclean_max_directory_size": "Maximum directory size (MB)",
|
||||
"autoclean_description_max_directory_size": "The maximum MB's of recordings stored.",
|
||||
"fragmentedrecordings": "Fragmented recordings",
|
||||
"description_fragmentedrecordings": "When recordings are fragmented they are suitable for an HLS stream. When turned on the MP4 container will look a bit different.",
|
||||
"fragmentedrecordings_enable": "Enable fragmentation",
|
||||
"fragmentedrecordings_description_enable": "Fragmented recordings are required for HLS.",
|
||||
"fragmentedrecordings_duration": "fragment duration",
|
||||
"fragmentedrecordings_description_duration": "Duration of a single fragment."
|
||||
"recording": "Gravação",
|
||||
"description_recording": "Especifique como você gostaria de fazer gravações. Gravar o tempo todo (24/7) ou baseada na detecção de movimento.",
|
||||
"continuous_recording": "Gravação contínua",
|
||||
"description_continuous_recording": "Faça gravações 24/7 ou baseadas na detecção de movimento.",
|
||||
"max_duration": "duração máxima do vídeo (segundos)",
|
||||
"description_max_duration": "A duração máxima de uma gravação.",
|
||||
"pre_recording": "pré-gravação (key frames em buffer)",
|
||||
"description_pre_recording": "Segundos antes de um evento ocorrer.",
|
||||
"post_recording": "pós gravação (segundos)",
|
||||
"description_post_recording": "Segundos após a ocorrência de um evento.",
|
||||
"threshold": "Limite de gravação (pixels)",
|
||||
"description_threshold": "O número de pixels alterados para gravar",
|
||||
"autoclean": "Limpeza automática",
|
||||
"description_autoclean": "Especifique se o Agente Kerberos pode limpar gravações quando uma capacidade de armazenamento específica (MB) é atingida. Isso removerá as gravações mais antigas quando a capacidade for atingida.",
|
||||
"autoclean_enable": "Ativar limpeza automática",
|
||||
"autoclean_description_enable": "Remova a gravação mais antiga quando a capacidade for atingida.",
|
||||
"autoclean_max_directory_size": "Tamanho máximo do diretório (MB)",
|
||||
"autoclean_description_max_directory_size": "O máximo de MB de gravações armazenadas.",
|
||||
"fragmentedrecordings": "Gravações Fragmentadas",
|
||||
"description_fragmentedrecordings": "Quando as gravações são fragmentadas, elas são adequadas para um fluxo HLS. Quando ativado, o contêiner MP4 parecerá um pouco diferente.",
|
||||
"fragmentedrecordings_enable": "Ativar fragmentação",
|
||||
"fragmentedrecordings_description_enable": "Gravações fragmentadas são necessárias para HLS.",
|
||||
"fragmentedrecordings_duration": "Duração do fragmento",
|
||||
"fragmentedrecordings_description_duration": "Duração de um único fragmento."
|
||||
},
|
||||
"streaming": {
|
||||
"stun_turn": "STUN/TURN for WebRTC",
|
||||
"description_stun_turn": "For full-resolution livestreaming we use the concept of WebRTC. One of the key capabilities is the ICE-candidate feature, which allows NAT traversal using the concepts of STUN/TURN.",
|
||||
"stun_server": "STUN server",
|
||||
"turn_server": "TURN server",
|
||||
"turn_username": "Username",
|
||||
"turn_password": "Password",
|
||||
"stun_turn_forward": "Forwarding and transcoding",
|
||||
"stun_turn_description_forward": "Optimisations and enhancements for TURN/STUN communication.",
|
||||
"stun_turn_webrtc": "Forwarding to WebRTC broker",
|
||||
"stun_turn_description_webrtc": "Forward h264 stream through MQTT",
|
||||
"stun_turn_transcode": "Transcode stream",
|
||||
"stun_turn_description_transcode": "Convert stream to a lower resolution",
|
||||
"stun_turn_downscale": "Downscale resolution (in % or original resolution)",
|
||||
"stun_turn": "STUN/TURN para WebRTC",
|
||||
"description_stun_turn": "Para transmissão ao vivo de resolução total, usamos o conceito de WebRTC. Um dos principais recursos é o recurso ICE-candidate, que permite a travessia de NAT usando os conceitos de STUN/TURN.",
|
||||
"stun_server": "Servidor STUN",
|
||||
"turn_server": "Servidor TURN",
|
||||
"turn_username": "Usuario",
|
||||
"turn_password": "Senha",
|
||||
"stun_turn_forward": "Encaminhamento e transcodificação",
|
||||
"stun_turn_description_forward": "Otimizações e melhorias para a comunicação TURN/STUN.",
|
||||
"stun_turn_webrtc": "Encaminhamento para broker WebRTC",
|
||||
"stun_turn_description_webrtc": "Encaminhar stream h264 através do MQTT",
|
||||
"stun_turn_transcode": "Transcodificar stream",
|
||||
"stun_turn_description_transcode": "Converter stream para uma resolução mais baixa",
|
||||
"stun_turn_downscale": "Resolução reduzida (em % ou resolução original)",
|
||||
"mqtt": "MQTT",
|
||||
"description_mqtt": "A MQTT broker is used to communicate from",
|
||||
"description2_mqtt": "to the Kerberos Agent, to achieve for example livestreaming or ONVIF (PTZ) capabilities.",
|
||||
"mqtt_brokeruri": "Broker Uri",
|
||||
"mqtt_username": "Username",
|
||||
"mqtt_password": "Password"
|
||||
"description_mqtt": "Um broker MQTT é usado para se comunicar de",
|
||||
"description2_mqtt": "para o Kerberos Agent, para obter, por exemplo, recursos de transmissão ao vivo ou ONVIF (PTZ).",
|
||||
"mqtt_brokeruri": "Url Broker",
|
||||
"mqtt_username": "Usuario",
|
||||
"mqtt_password": "Senha"
|
||||
},
|
||||
"conditions": {
|
||||
"timeofinterest": "Time Of Interest",
|
||||
"description_timeofinterest": "Only make recordings between specific time intervals (based on timezone).",
|
||||
"timeofinterest_enabled": "Enabled",
|
||||
"timeofinterest_description_enabled": "If enabled you can specify time windows",
|
||||
"sunday": "Sunday",
|
||||
"monday": "Monday",
|
||||
"tuesday": "Tuesday",
|
||||
"wednesday": "Wednesday",
|
||||
"thursday": "Thursday",
|
||||
"friday": "Friday",
|
||||
"saturday": "Saturday",
|
||||
"externalcondition": "External Condition",
|
||||
"description_externalcondition": "Depending on an external webservice recording can be enabled or disabled.",
|
||||
"regionofinterest": "Region Of Interest",
|
||||
"description_regionofinterest": "By defining one or more regions, motion will be tracked only in the regions you have defined."
|
||||
"timeofinterest": "Tempo de interesse",
|
||||
"description_timeofinterest": "Apenas faça gravações entre intervalos de tempo específicos (com base no fuso horário).",
|
||||
"timeofinterest_enabled": "Habilitado",
|
||||
"timeofinterest_description_enabled": "Se ativado, você pode especificar os intervalos de tempo",
|
||||
"sunday": "Domingo",
|
||||
"monday": "Segunda",
|
||||
"tuesday": "Terça",
|
||||
"wednesday": "Quarta",
|
||||
"thursday": "Quinta",
|
||||
"friday": "Sexta",
|
||||
"saturday": "Sabado",
|
||||
"externalcondition": "Condição Externa",
|
||||
"description_externalcondition": "Dependendo de um serviço de web(API) externo, a gravação pode ser habilitada ou desabilitada.",
|
||||
"regionofinterest": "Região de interesse",
|
||||
"description_regionofinterest": "Ao definir uma ou mais regiões, o movimento será rastreado apenas nas regiões definidas por você."
|
||||
},
|
||||
"persistence": {
|
||||
"kerberoshub": "Kerberos Hub",
|
||||
"description_kerberoshub": "Kerberos Agents can send heartbeats to a central",
|
||||
"description2_kerberoshub": "installation. Heartbeats and other relevant information are synced to Kerberos Hub to show realtime information about your video landscape.",
|
||||
"persistence": "Persistence",
|
||||
"saasoffering": "Kerberos Hub (SAAS offering)",
|
||||
"description_persistence": "Having the ability to store your recordings is the beginning of everything. You can choose between our",
|
||||
"description2_persistence": ", or a 3rd party provider",
|
||||
"select_persistence": "Select a persistence",
|
||||
"kerberoshub_proxyurl": "Kerberos Hub Proxy URL",
|
||||
"kerberoshub_description_proxyurl": "The Proxy endpoint for uploading your recordings.",
|
||||
"kerberoshub_apiurl": "Kerberos Hub API URL",
|
||||
"kerberoshub_description_apiurl": "The API endpoint for uploading your recordings.",
|
||||
"kerberoshub_publickey": "Public key",
|
||||
"kerberoshub_description_publickey": "The public key granted to your Kerberos Hub account.",
|
||||
"kerberoshub_privatekey": "Private key",
|
||||
"kerberoshub_description_privatekey": "The private key granted to your Kerberos Hub account.",
|
||||
"kerberoshub_site": "Site",
|
||||
"kerberoshub_description_site": "The site ID the Kerberos Agents are belonging to in Kerberos Hub.",
|
||||
"kerberoshub_region": "Region",
|
||||
"kerberoshub_description_region": "The region we are storing our recordings in.",
|
||||
"description_kerberoshub": "Agentes Kerberos podem enviar sinais constantes para a central",
|
||||
"description2_kerberoshub": "instalação. Os sinais e outras informações relevantes são sincronizadas com o Kerberos Hub para mostrar informações em tempo real sobre seu cenário de vídeo.",
|
||||
"persistence": "Armazenamento",
|
||||
"saasoffering": "Kerberos Hub (oferta SAAS)",
|
||||
"description_persistence": "Ter a capacidade de armazenar suas gravações é o começo de tudo. Você pode escolher entre nossos",
|
||||
"description2_persistence": ", ou um provedor terceirizado",
|
||||
"select_persistence": "Selecione um provedor de armazenamento",
|
||||
"kerberoshub_proxyurl": "Url proxy para Kerberos Hub",
|
||||
"kerberoshub_description_proxyurl": "O endpoint Proxy para enviar suas gravações.",
|
||||
"kerberoshub_apiurl": "Url de API do Kerberos Hub",
|
||||
"kerberoshub_description_apiurl": "O endpoint da API para enviar suas gravações.",
|
||||
"kerberoshub_publickey": "Chave pública (Public key)",
|
||||
"kerberoshub_description_publickey": "A chave pública concedida à sua conta do Kerberos Hub.",
|
||||
"kerberoshub_privatekey": "Chave privada (Private key)",
|
||||
"kerberoshub_description_privatekey": "A chave privada concedida à sua conta do Kerberos Hub.",
|
||||
"kerberoshub_site": "Local",
|
||||
"kerberoshub_description_site": "O ID do local ao qual os Agentes Kerberos pertencem no Hub Kerberos.",
|
||||
"kerberoshub_region": "Região",
|
||||
"kerberoshub_description_region": "A região em que estamos armazenando nossas gravações.",
|
||||
"kerberoshub_bucket": "Bucket",
|
||||
"kerberoshub_description_bucket": "The bucket we are storing our recordings in.",
|
||||
"kerberoshub_username": "Username/Directory",
|
||||
"kerberoshub_description_username": "The username of your Kerberos Hub account.",
|
||||
"kerberosvault_apiurl": "Kerberos Vault API URL",
|
||||
"kerberosvault_description_apiurl": "The Kerberos Vault API",
|
||||
"kerberosvault_provider": "Provider",
|
||||
"kerberosvault_description_provider": "The provider to which your recordings will be send.",
|
||||
"kerberosvault_directory": "Directory",
|
||||
"kerberosvault_description_directory": "Sub directory the recordings will be stored in your provider.",
|
||||
"kerberosvault_accesskey": "Access key",
|
||||
"kerberosvault_description_accesskey": "The access key of your Kerberos Vault account.",
|
||||
"kerberosvault_secretkey": "Secret key",
|
||||
"kerberosvault_description_secretkey": "The secret key of your Kerberos Vault account.",
|
||||
"verify_connection": "Verify Connection"
|
||||
"kerberoshub_description_bucket": "O bucket no qual estamos armazenando nossas gravações.",
|
||||
"kerberoshub_username": "Nome de usuário/diretório (Username/Directory)",
|
||||
"kerberoshub_description_username": "O nome de usuário da sua conta do Kerberos Hub.",
|
||||
"kerberosvault_apiurl": "Url da API do Kerberos Vault",
|
||||
"kerberosvault_description_apiurl": "a API Kerberos Vault",
|
||||
"kerberosvault_provider": "Provedor",
|
||||
"kerberosvault_description_provider": "O provedor para o qual suas gravações serão enviadas.",
|
||||
"kerberosvault_directory": "Diretório",
|
||||
"kerberosvault_description_directory": "Subdiretório as gravações serão armazenadas em seu provedor.",
|
||||
"kerberosvault_accesskey": "Chave de acesso(Access key)",
|
||||
"kerberosvault_description_accesskey": "A chave de acesso da sua conta do Kerberos Vault.",
|
||||
"kerberosvault_secretkey": "Chave secreta(Secret key)",
|
||||
"kerberosvault_description_secretkey": "A chave secreta da sua conta do Kerberos Vault.",
|
||||
"verify_connection": "Verificar conexão"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ const LanguageSelect = () => {
|
||||
de: { label: 'Deutsch', dir: 'ltr', active: false },
|
||||
pt: { label: 'Português', dir: 'ltr', active: false },
|
||||
es: { label: 'Español', dir: 'ltr', active: false },
|
||||
ja: { label: '日本', dir: 'rlt', active: false },
|
||||
};
|
||||
|
||||
if (!languageMap[selected]) {
|
||||
|
||||
@@ -7,14 +7,16 @@ const websocketprotocol = protocol === 'http:' ? 'ws:' : 'wss:';
|
||||
|
||||
const dev = {
|
||||
ENV: 'dev',
|
||||
// Comment the below lines, when using codespaces or other special DNS names (which you can't control)
|
||||
HOSTNAME: hostname,
|
||||
API_URL: `${protocol}//${hostname}:8080/api`,
|
||||
URL: `${protocol}//${hostname}:8080`,
|
||||
WS_URL: `${websocketprotocol}//${hostname}:8080/ws`,
|
||||
|
||||
// Uncomment, and comment the above lines, when using codespaces or other special DNS names (which you can't control)
|
||||
// HOSTNAME: externalHost,
|
||||
// API_URL: `${protocol}//${externalHost}/api`,
|
||||
// URL: `${protocol}//${externalHost}`,
|
||||
// WS_URL: `${websocketprotocol}//${externalHost}/ws`,
|
||||
};
|
||||
|
||||
const prod = {
|
||||
|
||||
@@ -14,7 +14,7 @@ i18n
|
||||
escapeValue: false,
|
||||
},
|
||||
load: 'languageOnly',
|
||||
whitelist: ['de', 'en', 'nl', 'fr', 'pl', 'es', 'pt'],
|
||||
whitelist: ['de', 'en', 'nl', 'fr', 'pl', 'es', 'pt', 'ja'],
|
||||
});
|
||||
|
||||
export default i18n;
|
||||
|
||||
Reference in New Issue
Block a user