From cc075d7237e8282e3b2c83cdb148fff1b2a99382 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Verstraeten?= Date: Wed, 6 Aug 2025 14:42:23 +0000 Subject: [PATCH 1/6] Refactor IPCamera configuration to include BaseWidth and BaseHeight; update image resizing logic to use dynamic width based on configuration --- machinery/src/capture/main.go | 4 ++-- machinery/src/cloud/Cloud.go | 4 ++-- machinery/src/components/Kerberos.go | 21 ++++++++++++++++++--- machinery/src/config/main.go | 3 +++ machinery/src/models/Config.go | 19 ++++++++++++------- machinery/src/routers/mqtt/main.go | 8 -------- machinery/src/routers/websocket/main.go | 2 +- machinery/src/utils/main.go | 14 ++++++++++++-- 8 files changed, 50 insertions(+), 25 deletions(-) diff --git a/machinery/src/capture/main.go b/machinery/src/capture/main.go index 4ad180c..57043b2 100644 --- a/machinery/src/capture/main.go +++ b/machinery/src/capture/main.go @@ -727,7 +727,7 @@ func VerifyCamera(c *gin.Context) { } } -func Base64Image(captureDevice *Capture, communication *models.Communication) string { +func Base64Image(captureDevice *Capture, communication *models.Communication, configuration *models.Configuration) string { // We'll try to get a snapshot from the camera. var queue *packets.Queue var cursor *packets.QueueCursor @@ -757,7 +757,7 @@ func Base64Image(captureDevice *Capture, communication *models.Communication) st var img image.YCbCr img, err = (*rtspClient).DecodePacket(pkt) if err == nil { - imageResized, _ := utils.ResizeImage(&img, 100000) + imageResized, _ := utils.ResizeImage(&img, uint(configuration.Config.Capture.IPCamera.BaseWidth)) bytes, _ := utils.ImageToBytes(imageResized) encodedImage = base64.StdEncoding.EncodeToString(bytes) break diff --git a/machinery/src/cloud/Cloud.go b/machinery/src/cloud/Cloud.go index e830cc8..692ce74 100644 --- a/machinery/src/cloud/Cloud.go +++ b/machinery/src/cloud/Cloud.go @@ -706,7 +706,7 @@ func HandleLiveStreamSD(livestreamCursor *packets.QueueCursor, configuration *mo log.Log.Info("cloud.HandleLiveStreamSD(): Sending base64 encoded images to MQTT.") img, err := rtspClient.DecodePacket(pkt) if err == nil { - imageResized, _ := utils.ResizeImage(&img, 100000) + imageResized, _ := utils.ResizeImage(&img, uint(config.Capture.IPCamera.BaseWidth)) bytes, _ := utils.ImageToBytes(imageResized) chunking := config.Capture.LiveviewChunking @@ -865,7 +865,7 @@ func HandleRealtimeProcessing(processingCursor *packets.QueueCursor, configurati log.Log.Info("cloud.RealtimeProcessing(): Sending base64 encoded images to MQTT.") img, err := rtspClient.DecodePacket(pkt) if err == nil { - imageResized, _ := utils.ResizeImage(&img, 100000) + imageResized, _ := utils.ResizeImage(&img, uint(config.Capture.IPCamera.BaseWidth)) bytes, _ := utils.ImageToBytes(imageResized) encoded := base64.StdEncoding.EncodeToString(bytes) diff --git a/machinery/src/components/Kerberos.go b/machinery/src/components/Kerberos.go index e29c380..492ccca 100644 --- a/machinery/src/components/Kerberos.go +++ b/machinery/src/components/Kerberos.go @@ -173,6 +173,13 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu configuration.Config.Capture.IPCamera.Width = width configuration.Config.Capture.IPCamera.Height = height + // Set the liveview width and height, this is used for the liveview and motion regions (drawing on the hub). + liveviewWidth := config.Capture.IPCamera.BaseWidth + if liveviewWidth > 0 { + widthAspectRatio := float64(liveviewWidth) / float64(width) + configuration.Config.Capture.IPCamera.BaseHeight = int(float64(height) * widthAspectRatio) + } + // Set the SPS and PPS values in the configuration. configuration.Config.Capture.IPCamera.SPSNALUs = [][]byte{videoStream.SPS} configuration.Config.Capture.IPCamera.PPSNALUs = [][]byte{videoStream.PPS} @@ -226,6 +233,14 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu // Set config values as well configuration.Config.Capture.IPCamera.SubWidth = width configuration.Config.Capture.IPCamera.SubHeight = height + + // If we have a substream, we need to set the width and height of the substream. (so we will override above information) + // Set the liveview width and height, this is used for the liveview and motion regions (drawing on the hub). + liveviewWidth := config.Capture.IPCamera.BaseWidth + if liveviewWidth > 0 { + widthAspectRatio := float64(liveviewWidth) / float64(width) + configuration.Config.Capture.IPCamera.BaseHeight = int(float64(height) * widthAspectRatio) + } } // We are creating a queue to store the RTSP frames in, these frames will be @@ -676,7 +691,7 @@ func MakeRecording(c *gin.Context, communication *models.Communication) { // @Success 200 func GetSnapshotBase64(c *gin.Context, captureDevice *capture.Capture, configuration *models.Configuration, communication *models.Communication) { // We'll try to get a snapshot from the camera. - base64Image := capture.Base64Image(captureDevice, communication) + base64Image := capture.Base64Image(captureDevice, communication, configuration) if base64Image != "" { communication.Image = base64Image } @@ -698,7 +713,7 @@ func GetSnapshotRaw(c *gin.Context, captureDevice *capture.Capture, configuratio image := capture.JpegImage(captureDevice, communication) // encode image to jpeg - imageResized, _ := utils.ResizeImage(&image, 100000) + imageResized, _ := utils.ResizeImage(&image, uint(configuration.Config.Capture.IPCamera.BaseWidth)) bytes, _ := utils.ImageToBytes(imageResized) // Return image/jpeg @@ -714,7 +729,7 @@ func GetSnapshotRaw(c *gin.Context, captureDevice *capture.Capture, configuratio // @Success 200 func GetConfig(c *gin.Context, captureDevice *capture.Capture, configuration *models.Configuration, communication *models.Communication) { // We'll try to get a snapshot from the camera. - base64Image := capture.Base64Image(captureDevice, communication) + base64Image := capture.Base64Image(captureDevice, communication, configuration) if base64Image != "" { communication.Image = base64Image } diff --git a/machinery/src/config/main.go b/machinery/src/config/main.go index 404d950..f194347 100644 --- a/machinery/src/config/main.go +++ b/machinery/src/config/main.go @@ -544,6 +544,9 @@ func OverrideWithEnvironmentVariables(configuration *models.Configuration) { PrivateKey: "-----BEGIN PRIVATE KEY-----\n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\n-----END PRIVATE KEY-----", } } + + // Hardcoded values, should be configurable in the future. + configuration.Config.Capture.IPCamera.BaseWidth = 640 } func SaveConfig(configDirectory string, config models.Config, configuration *models.Configuration, communication *models.Communication) error { diff --git a/machinery/src/models/Config.go b/machinery/src/models/Config.go index 97748ed..3759120 100644 --- a/machinery/src/models/Config.go +++ b/machinery/src/models/Config.go @@ -79,13 +79,18 @@ type Capture struct { // IPCamera configuration, such as the RTSP url of the IPCamera and the FPS. // Also includes ONVIF integration type IPCamera struct { - RTSP string `json:"rtsp"` - Width int `json:"width"` - Height int `json:"height"` - FPS string `json:"fps"` - SubRTSP string `json:"sub_rtsp"` - SubWidth int `json:"sub_width"` - SubHeight int `json:"sub_height"` + RTSP string `json:"rtsp"` + Width int `json:"width"` + Height int `json:"height"` + FPS string `json:"fps"` + + SubRTSP string `json:"sub_rtsp"` + SubWidth int `json:"sub_width"` + SubHeight int `json:"sub_height"` + + BaseWidth int `json:"base_width"` + BaseHeight int `json:"base_height"` + SubFPS string `json:"sub_fps"` ONVIF string `json:"onvif,omitempty" bson:"onvif"` ONVIFXAddr string `json:"onvif_xaddr" bson:"onvif_xaddr"` diff --git a/machinery/src/routers/mqtt/main.go b/machinery/src/routers/mqtt/main.go index a974c6c..957fd3c 100644 --- a/machinery/src/routers/mqtt/main.go +++ b/machinery/src/routers/mqtt/main.go @@ -389,14 +389,6 @@ func HandleRequestConfig(mqttClient mqtt.Client, hubKey string, payload models.P // Copy the config, as we don't want to share the encryption part. deepCopy := configuration.Config - // We need a fix for the width and height if a substream. - // The ROI requires the width and height of the sub stream. - if configuration.Config.Capture.IPCamera.SubRTSP != "" && - configuration.Config.Capture.IPCamera.SubRTSP != configuration.Config.Capture.IPCamera.RTSP { - deepCopy.Capture.IPCamera.Width = configuration.Config.Capture.IPCamera.SubWidth - deepCopy.Capture.IPCamera.Height = configuration.Config.Capture.IPCamera.SubHeight - } - var configMap map[string]interface{} inrec, _ := json.Marshal(deepCopy) json.Unmarshal(inrec, &configMap) diff --git a/machinery/src/routers/websocket/main.go b/machinery/src/routers/websocket/main.go index c8b9ffc..2a0442f 100644 --- a/machinery/src/routers/websocket/main.go +++ b/machinery/src/routers/websocket/main.go @@ -159,7 +159,7 @@ logreader: var img image.YCbCr img, err = (*rtspClient).DecodePacket(pkt) if err == nil { - imageResized, _ := utils.ResizeImage(&img, 100000) + imageResized, _ := utils.ResizeImage(&img, 640) // Resize to 640 width bytes, _ := utils.ImageToBytes(imageResized) encodedImage = base64.StdEncoding.EncodeToString(bytes) } else { diff --git a/machinery/src/utils/main.go b/machinery/src/utils/main.go index 6213b56..8ce54de 100644 --- a/machinery/src/utils/main.go +++ b/machinery/src/utils/main.go @@ -411,13 +411,23 @@ func ImageToBytes(img *image.Image) ([]byte, error) { return buffer.Bytes(), err } -func ResizeImage(img image.Image, maxSize uint64) (*image.Image, error) { +func ResizeImage(img image.Image, newWidth uint) (*image.Image, error) { if img == nil { return nil, errors.New("image is nil") } // resize to width 640 using Lanczos resampling // and preserve aspect ratio - m := resize.Resize(640, 0, img, resize.Lanczos3) + m := resize.Resize(newWidth, 0, img, resize.Lanczos3) return &m, nil } + +func ResizeHeightWithAspectRatio(newWidth int, width int, height int) (int, int) { + if newWidth <= 0 || width <= 0 || height <= 0 { + return width, height + } + // Calculate the new height based on the aspect ratio + newHeight := (newWidth * height) / width + // Return the new dimensions + return newWidth, newHeight +} From 08d191e54215c74174a7748f52ae0807357b2bd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Verstraeten?= Date: Mon, 11 Aug 2025 08:08:39 +0000 Subject: [PATCH 2/6] Update image resizing to support dynamic height; modify related functions and configurations --- machinery/src/capture/main.go | 2 +- machinery/src/cloud/Cloud.go | 4 ++-- machinery/src/components/Kerberos.go | 2 +- machinery/src/config/main.go | 1 + machinery/src/routers/http/routes.go | 2 +- machinery/src/routers/websocket/main.go | 10 ++++++---- machinery/src/utils/main.go | 4 ++-- 7 files changed, 14 insertions(+), 11 deletions(-) diff --git a/machinery/src/capture/main.go b/machinery/src/capture/main.go index 57043b2..4315bfb 100644 --- a/machinery/src/capture/main.go +++ b/machinery/src/capture/main.go @@ -757,7 +757,7 @@ func Base64Image(captureDevice *Capture, communication *models.Communication, co var img image.YCbCr img, err = (*rtspClient).DecodePacket(pkt) if err == nil { - imageResized, _ := utils.ResizeImage(&img, uint(configuration.Config.Capture.IPCamera.BaseWidth)) + imageResized, _ := utils.ResizeImage(&img, uint(configuration.Config.Capture.IPCamera.BaseWidth), uint(configuration.Config.Capture.IPCamera.BaseHeight)) bytes, _ := utils.ImageToBytes(imageResized) encodedImage = base64.StdEncoding.EncodeToString(bytes) break diff --git a/machinery/src/cloud/Cloud.go b/machinery/src/cloud/Cloud.go index 692ce74..e800ec2 100644 --- a/machinery/src/cloud/Cloud.go +++ b/machinery/src/cloud/Cloud.go @@ -706,7 +706,7 @@ func HandleLiveStreamSD(livestreamCursor *packets.QueueCursor, configuration *mo log.Log.Info("cloud.HandleLiveStreamSD(): Sending base64 encoded images to MQTT.") img, err := rtspClient.DecodePacket(pkt) if err == nil { - imageResized, _ := utils.ResizeImage(&img, uint(config.Capture.IPCamera.BaseWidth)) + imageResized, _ := utils.ResizeImage(&img, uint(config.Capture.IPCamera.BaseWidth), uint(config.Capture.IPCamera.BaseHeight)) bytes, _ := utils.ImageToBytes(imageResized) chunking := config.Capture.LiveviewChunking @@ -865,7 +865,7 @@ func HandleRealtimeProcessing(processingCursor *packets.QueueCursor, configurati log.Log.Info("cloud.RealtimeProcessing(): Sending base64 encoded images to MQTT.") img, err := rtspClient.DecodePacket(pkt) if err == nil { - imageResized, _ := utils.ResizeImage(&img, uint(config.Capture.IPCamera.BaseWidth)) + imageResized, _ := utils.ResizeImage(&img, uint(config.Capture.IPCamera.BaseWidth), uint(config.Capture.IPCamera.BaseHeight)) bytes, _ := utils.ImageToBytes(imageResized) encoded := base64.StdEncoding.EncodeToString(bytes) diff --git a/machinery/src/components/Kerberos.go b/machinery/src/components/Kerberos.go index 492ccca..05dfa2e 100644 --- a/machinery/src/components/Kerberos.go +++ b/machinery/src/components/Kerberos.go @@ -713,7 +713,7 @@ func GetSnapshotRaw(c *gin.Context, captureDevice *capture.Capture, configuratio image := capture.JpegImage(captureDevice, communication) // encode image to jpeg - imageResized, _ := utils.ResizeImage(&image, uint(configuration.Config.Capture.IPCamera.BaseWidth)) + imageResized, _ := utils.ResizeImage(&image, uint(configuration.Config.Capture.IPCamera.BaseWidth), uint(configuration.Config.Capture.IPCamera.BaseHeight)) bytes, _ := utils.ImageToBytes(imageResized) // Return image/jpeg diff --git a/machinery/src/config/main.go b/machinery/src/config/main.go index f194347..054c790 100644 --- a/machinery/src/config/main.go +++ b/machinery/src/config/main.go @@ -547,6 +547,7 @@ func OverrideWithEnvironmentVariables(configuration *models.Configuration) { // Hardcoded values, should be configurable in the future. configuration.Config.Capture.IPCamera.BaseWidth = 640 + configuration.Config.Capture.IPCamera.BaseHeight = 0 // we might force the height to 0, so it will be calculated based on the width and aspect ratio of the camera. } func SaveConfig(configDirectory string, config models.Config, configuration *models.Configuration, communication *models.Communication) error { diff --git a/machinery/src/routers/http/routes.go b/machinery/src/routers/http/routes.go index 612f34a..71d1ba3 100644 --- a/machinery/src/routers/http/routes.go +++ b/machinery/src/routers/http/routes.go @@ -15,7 +15,7 @@ import ( func AddRoutes(r *gin.Engine, authMiddleware *jwt.GinJWTMiddleware, configDirectory string, configuration *models.Configuration, communication *models.Communication, captureDevice *capture.Capture) *gin.RouterGroup { r.GET("/ws", func(c *gin.Context) { - websocket.WebsocketHandler(c, communication, captureDevice) + websocket.WebsocketHandler(c, configuration, communication, captureDevice) }) // This is legacy should be removed in future! Now everything diff --git a/machinery/src/routers/websocket/main.go b/machinery/src/routers/websocket/main.go index 2a0442f..f54ffd2 100644 --- a/machinery/src/routers/websocket/main.go +++ b/machinery/src/routers/websocket/main.go @@ -49,7 +49,7 @@ var upgrader = websocket.Upgrader{ }, } -func WebsocketHandler(c *gin.Context, communication *models.Communication, captureDevice *capture.Capture) { +func WebsocketHandler(c *gin.Context, configuration *models.Configuration, communication *models.Communication, captureDevice *capture.Capture) { w := c.Writer r := c.Request conn, err := upgrader.Upgrade(w, r, nil) @@ -112,7 +112,7 @@ func WebsocketHandler(c *gin.Context, communication *models.Communication, captu ctx, cancel := context.WithCancel(context.Background()) sockets[clientID].Cancels["stream-sd"] = cancel - go ForwardSDStream(ctx, clientID, sockets[clientID], communication, captureDevice) + go ForwardSDStream(ctx, clientID, sockets[clientID], configuration, communication, captureDevice) } } } @@ -131,7 +131,7 @@ func WebsocketHandler(c *gin.Context, communication *models.Communication, captu } } -func ForwardSDStream(ctx context.Context, clientID string, connection *Connection, communication *models.Communication, captureDevice *capture.Capture) { +func ForwardSDStream(ctx context.Context, clientID string, connection *Connection, configuration *models.Configuration, communication *models.Communication, captureDevice *capture.Capture) { var queue *packets.Queue var cursor *packets.QueueCursor @@ -159,7 +159,9 @@ logreader: var img image.YCbCr img, err = (*rtspClient).DecodePacket(pkt) if err == nil { - imageResized, _ := utils.ResizeImage(&img, 640) // Resize to 640 width + config := configuration.Config + // Resize the image to the base width and height + imageResized, _ := utils.ResizeImage(&img, uint(config.Capture.IPCamera.BaseHeight), uint(config.Capture.IPCamera.BaseWidth)) bytes, _ := utils.ImageToBytes(imageResized) encodedImage = base64.StdEncoding.EncodeToString(bytes) } else { diff --git a/machinery/src/utils/main.go b/machinery/src/utils/main.go index 8ce54de..11a6e38 100644 --- a/machinery/src/utils/main.go +++ b/machinery/src/utils/main.go @@ -411,14 +411,14 @@ func ImageToBytes(img *image.Image) ([]byte, error) { return buffer.Bytes(), err } -func ResizeImage(img image.Image, newWidth uint) (*image.Image, error) { +func ResizeImage(img image.Image, newWidth uint, newHeight uint) (*image.Image, error) { if img == nil { return nil, errors.New("image is nil") } // resize to width 640 using Lanczos resampling // and preserve aspect ratio - m := resize.Resize(newWidth, 0, img, resize.Lanczos3) + m := resize.Resize(newWidth, newHeight, img, resize.Lanczos3) return &m, nil } From aa925d5c9b6fd436e0d285282aad4a5c3f39f82a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Verstraeten?= Date: Mon, 11 Aug 2025 09:23:11 +0000 Subject: [PATCH 3/6] Add BaseWidth and BaseHeight configuration options for IPCamera; update resizing logic in RunAgent and websocket handlers --- README.md | 2 ++ machinery/src/components/Kerberos.go | 22 ++++++++++++++++------ machinery/src/config/main.go | 14 +++++++++----- machinery/src/routers/websocket/main.go | 2 +- 4 files changed, 28 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index e61aee3..08c2283 100644 --- a/README.md +++ b/README.md @@ -208,6 +208,8 @@ Next to attaching the configuration file, it is also possible to override the co | `AGENT_REGION_POLYGON` | A single polygon set for motion detection: "x1,y1;x2,y2;x3,y3;... | "" | | `AGENT_CAPTURE_IPCAMERA_RTSP` | Full-HD RTSP endpoint to the camera you're targetting. | "" | | `AGENT_CAPTURE_IPCAMERA_SUB_RTSP` | Sub-stream RTSP endpoint used for livestreaming (WebRTC). | "" | +| `AGENT_CAPTURE_IPCAMERA_BASE_WIDTH` | Force a specific width resolution for live view processing. | "" | +| `AGENT_CAPTURE_IPCAMERA_BASE_HEIGHT` | Force a specific height resolution for live view processing. | "" | | `AGENT_CAPTURE_IPCAMERA_ONVIF` | Mark as a compliant ONVIF device. | "" | | `AGENT_CAPTURE_IPCAMERA_ONVIF_XADDR` | ONVIF endpoint/address running on the camera. | "" | | `AGENT_CAPTURE_IPCAMERA_ONVIF_USERNAME` | ONVIF username to authenticate against. | "" | diff --git a/machinery/src/components/Kerberos.go b/machinery/src/components/Kerberos.go index 05dfa2e..0777493 100644 --- a/machinery/src/components/Kerberos.go +++ b/machinery/src/components/Kerberos.go @@ -174,10 +174,15 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu configuration.Config.Capture.IPCamera.Height = height // Set the liveview width and height, this is used for the liveview and motion regions (drawing on the hub). - liveviewWidth := config.Capture.IPCamera.BaseWidth - if liveviewWidth > 0 { - widthAspectRatio := float64(liveviewWidth) / float64(width) + baseWidth := config.Capture.IPCamera.BaseWidth + baseHeight := config.Capture.IPCamera.BaseHeight + // If the liveview height is not set, we will calculate it based on the width and aspect ratio of the camera. + if baseWidth > 0 && baseHeight == 0 { + widthAspectRatio := float64(baseWidth) / float64(width) configuration.Config.Capture.IPCamera.BaseHeight = int(float64(height) * widthAspectRatio) + } else { + configuration.Config.Capture.IPCamera.BaseHeight = height + configuration.Config.Capture.IPCamera.BaseWidth = width } // Set the SPS and PPS values in the configuration. @@ -236,10 +241,15 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu // If we have a substream, we need to set the width and height of the substream. (so we will override above information) // Set the liveview width and height, this is used for the liveview and motion regions (drawing on the hub). - liveviewWidth := config.Capture.IPCamera.BaseWidth - if liveviewWidth > 0 { - widthAspectRatio := float64(liveviewWidth) / float64(width) + baseWidth := config.Capture.IPCamera.BaseWidth + baseHeight := config.Capture.IPCamera.BaseHeight + // If the liveview height is not set, we will calculate it based on the width and aspect ratio of the camera. + if baseWidth > 0 && baseHeight == 0 { + widthAspectRatio := float64(baseWidth) / float64(width) configuration.Config.Capture.IPCamera.BaseHeight = int(float64(height) * widthAspectRatio) + } else { + configuration.Config.Capture.IPCamera.BaseHeight = height + configuration.Config.Capture.IPCamera.BaseWidth = width } } diff --git a/machinery/src/config/main.go b/machinery/src/config/main.go index 054c790..ec623f8 100644 --- a/machinery/src/config/main.go +++ b/machinery/src/config/main.go @@ -239,7 +239,15 @@ func OverrideWithEnvironmentVariables(configuration *models.Configuration) { configuration.Config.Capture.IPCamera.SubRTSP = value break - /* ONVIF connnection settings */ + /* Base width and height for the liveview and motion regions */ + case "AGENT_CAPTURE_IPCAMERA_BASE_WIDTH": + configuration.Config.Capture.IPCamera.BaseWidth, _ = strconv.Atoi(value) + break + case "AGENT_CAPTURE_IPCAMERA_BASE_HEIGHT": + configuration.Config.Capture.IPCamera.BaseHeight, _ = strconv.Atoi(value) + break + + /* ONVIF connnection settings */ case "AGENT_CAPTURE_IPCAMERA_ONVIF": configuration.Config.Capture.IPCamera.ONVIF = value break @@ -544,10 +552,6 @@ func OverrideWithEnvironmentVariables(configuration *models.Configuration) { PrivateKey: "-----BEGIN PRIVATE KEY-----\n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\n-----END PRIVATE KEY-----", } } - - // Hardcoded values, should be configurable in the future. - configuration.Config.Capture.IPCamera.BaseWidth = 640 - configuration.Config.Capture.IPCamera.BaseHeight = 0 // we might force the height to 0, so it will be calculated based on the width and aspect ratio of the camera. } func SaveConfig(configDirectory string, config models.Config, configuration *models.Configuration, communication *models.Communication) error { diff --git a/machinery/src/routers/websocket/main.go b/machinery/src/routers/websocket/main.go index f54ffd2..3541b7a 100644 --- a/machinery/src/routers/websocket/main.go +++ b/machinery/src/routers/websocket/main.go @@ -161,7 +161,7 @@ logreader: if err == nil { config := configuration.Config // Resize the image to the base width and height - imageResized, _ := utils.ResizeImage(&img, uint(config.Capture.IPCamera.BaseHeight), uint(config.Capture.IPCamera.BaseWidth)) + imageResized, _ := utils.ResizeImage(&img, uint(config.Capture.IPCamera.BaseWidth), uint(config.Capture.IPCamera.BaseHeight)) bytes, _ := utils.ImageToBytes(imageResized) encodedImage = base64.StdEncoding.EncodeToString(bytes) } else { From ed582a9d57fab576780206283e7980bce9df1ab7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Verstraeten?= Date: Mon, 11 Aug 2025 09:38:24 +0000 Subject: [PATCH 4/6] Resize polygon coordinates based on IPCamera BaseWidth and BaseHeight configuration --- machinery/src/computervision/main.go | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/machinery/src/computervision/main.go b/machinery/src/computervision/main.go index 769bd42..ed8119f 100644 --- a/machinery/src/computervision/main.go +++ b/machinery/src/computervision/main.go @@ -63,16 +63,34 @@ func ProcessMotion(motionCursor *packets.QueueCursor, configuration *models.Conf } } + // A user might have set the base width and height for the IPCamera. + // This means also the polygon coordinates are set to a specific width and height (which might be different than the actual packets + // received from the IPCamera). So we will resize the polygon coordinates to the base width and height. + baseWidthRatio := 1.0 + baseHeightRatio := 1.0 + baseWidth := config.Capture.IPCamera.BaseWidth + baseHeight := config.Capture.IPCamera.BaseHeight + if baseWidth > 0 && baseHeight > 0 { + // We'll get the first image to calculate the ratio + img := imageArray[0] + if img != nil { + bounds := img.Bounds() + rows := bounds.Dy() + cols := bounds.Dx() + baseWidthRatio = float64(cols) / float64(baseWidth) + baseHeightRatio = float64(rows) / float64(baseHeight) + } + } + // Calculate mask var polyObjects []geo.Polygon - if config.Region != nil { for _, polygon := range config.Region.Polygon { coords := polygon.Coordinates poly := geo.Polygon{} for _, c := range coords { - x := c.X - y := c.Y + x := c.X * baseWidthRatio + y := c.Y * baseHeightRatio p := geo.NewPoint(x, y) if !poly.Contains(p) { poly.Add(p) From d78e682759676196aa7da2891c0e006780438367 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Verstraeten?= Date: Mon, 11 Aug 2025 11:39:45 +0200 Subject: [PATCH 5/6] Update config.json --- machinery/data/config/config.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/machinery/data/config/config.json b/machinery/data/config/config.json index 34fa303..d408da4 100644 --- a/machinery/data/config/config.json +++ b/machinery/data/config/config.json @@ -14,7 +14,9 @@ "ipcamera": { "rtsp": "", "sub_rtsp": "", - "fps": "" + "fps": "", + "base_width": 640, + "base_height": 0 }, "usbcamera": { "device": "" @@ -120,4 +122,4 @@ "signing": {}, "realtimeprocessing": "false", "realtimeprocessing_topic": "" -} \ No newline at end of file +} From 6fba2ff05df649235d934595d84d7e56aecd0402 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Verstraeten?= Date: Mon, 11 Aug 2025 10:20:37 +0000 Subject: [PATCH 6/6] Refactor logging in gortsplib and mp4 modules to use Debug and Error levels; update free box size in MP4 initialization --- machinery/src/capture/gortsplib.go | 4 +- machinery/src/video/mp4.go | 70 ++++++++---------------------- 2 files changed, 21 insertions(+), 53 deletions(-) diff --git a/machinery/src/capture/gortsplib.go b/machinery/src/capture/gortsplib.go index 5519b2c..1d25817 100644 --- a/machinery/src/capture/gortsplib.go +++ b/machinery/src/capture/gortsplib.go @@ -654,7 +654,7 @@ func (g *Golibrtsp) Start(ctx context.Context, streamType string, queue *packets gopDuration := float64(keyframeInterval) / g.Streams[g.VideoH265Index].FPS gopSize := int(avgInterval) // Store GOP size in a separate variable g.Streams[g.VideoH264Index].GopSize = gopSize - log.Log.Info(fmt.Sprintf("capture.golibrtsp.Start(%s): Keyframe interval=%d packets, Avg=%.1f, GOP=%.1fs, GOPSize=%d", + log.Log.Debug(fmt.Sprintf("capture.golibrtsp.Start(%s): Keyframe interval=%d packets, Avg=%.1f, GOP=%.1fs, GOPSize=%d", streamType, keyframeInterval, avgInterval, gopDuration, gopSize)) preRecording := configuration.Config.Capture.PreRecording if preRecording > 0 && int(gopDuration) > 0 { @@ -799,7 +799,7 @@ func (g *Golibrtsp) Start(ctx context.Context, streamType string, queue *packets gopDuration := float64(keyframeInterval) / g.Streams[g.VideoH265Index].FPS gopSize := int(avgInterval) // Store GOP size in a separate variable g.Streams[g.VideoH265Index].GopSize = gopSize - log.Log.Info(fmt.Sprintf("capture.golibrtsp.Start(%s): Keyframe interval=%d packets, Avg=%.1f, GOP=%.1fs, GOPSize=%d", + log.Log.Debug(fmt.Sprintf("capture.golibrtsp.Start(%s): Keyframe interval=%d packets, Avg=%.1f, GOP=%.1fs, GOPSize=%d", streamType, keyframeInterval, avgInterval, gopDuration, gopSize)) preRecording := configuration.Config.Capture.PreRecording if preRecording > 0 && int(gopDuration) > 0 { diff --git a/machinery/src/video/mp4.go b/machinery/src/video/mp4.go index 174efa4..6324bf3 100644 --- a/machinery/src/video/mp4.go +++ b/machinery/src/video/mp4.go @@ -64,15 +64,14 @@ func NewMP4(fileName string, spsNALUs [][]byte, ppsNALUs [][]byte, vpsNALUs [][] init := mp4ff.NewMP4Init() // Add a free box to the init segment - // Prepend a free box to the init segment with a size of 1000 - freeBoxSize := 2048 + // Prepend a free box to the init segment with a size of 4096 bytes, so we can overwrite it later with the actual init segment. + freeBoxSize := 4096 free := mp4ff.NewFreeBox(make([]byte, freeBoxSize)) init.AddChild(free) // Create a writer ofd, err := os.Create(fileName) if err != nil { - panic(err) } // Create a buffered writer @@ -82,7 +81,6 @@ func NewMP4(fileName string, spsNALUs [][]byte, ppsNALUs [][]byte, vpsNALUs [][] // so we can overwrite it later with the actual init segment. err = init.Encode(bufferedWriter) if err != nil { - panic(err) } return &MP4{ @@ -142,7 +140,7 @@ func (mp4 *MP4) AddSampleToTrack(trackID uint32, isKeyframe bool, data []byte, p mp4.MoofBoxSizes = append(mp4.MoofBoxSizes, int64(mp4.Segment.Size())) err := mp4.Segment.Encode(mp4.Writer) if err != nil { - return err + log.Log.Error("mp4.AddSampleToTrack(): error encoding segment: " + err.Error()) } mp4.Segments = append(mp4.Segments, mp4.Segment) } @@ -158,6 +156,7 @@ func (mp4 *MP4) AddSampleToTrack(trackID uint32, isKeyframe bool, data []byte, p // Create a video fragment multiTrackFragment, err := mp4ff.CreateMultiTrackFragment(uint32(mp4.SegmentCount), mp4.TrackIDs) // Assuming 1 for video track and 2 for audio track if err != nil { + log.Log.Error("mp4.AddSampleToTrack(): error creating multi track fragment: " + err.Error()) } mp4.MultiTrackFragment = multiTrackFragment seg.AddFragment(multiTrackFragment) @@ -175,9 +174,10 @@ func (mp4 *MP4) AddSampleToTrack(trackID uint32, isKeyframe bool, data []byte, p var lengthPrefixed []byte var err error - if mp4.VideoTrackName == "H264" || mp4.VideoTrackName == "AVC1" { // Convert Annex B to length-prefixed NAL units if H264 + switch mp4.VideoTrackName { + case "H264", "AVC1": // Convert Annex B to length-prefixed NAL units if H264 lengthPrefixed, err = annexBToLengthPrefixed(data) - } else if mp4.VideoTrackName == "H265" || mp4.VideoTrackName == "HVC1" { // Convert H265 Annex B to length-prefixed NAL units + case "H265", "HVC1": // Convert H265 Annex B to length-prefixed NAL units lengthPrefixed, err = annexBToLengthPrefixed(data) } @@ -187,14 +187,12 @@ func (mp4 *MP4) AddSampleToTrack(trackID uint32, isKeyframe bool, data []byte, p log.Log.Debug("Adding sample to track " + fmt.Sprintf("%d, PTS: %d, Duration: %d, size: %d, Keyframe: %t", trackID, pts, duration, len(lengthPrefixed), isKeyframe)) mp4.LastVideoSampleDTS = duration - //fmt.Printf("Adding sample to track %d, PTS: %d, Duration: %d, size: %d, Keyframe: %t\n", trackID, pts, duration, len(mp4.VideoFullSample.Data), isKeyframe) mp4.VideoTotalDuration += duration mp4.VideoFullSample.DecodeTime = mp4.VideoTotalDuration - duration mp4.VideoFullSample.Sample.Dur = uint32(duration) err := mp4.MultiTrackFragment.AddFullSampleToTrack(*mp4.VideoFullSample, trackID) if err != nil { - //log.Printf("Error adding sample to track %d: %v", trackID, err) - return err + log.Log.Error("mp4.AddSampleToTrack(): error adding sample to track " + fmt.Sprintf("%d: %v", trackID, err)) } } @@ -261,33 +259,6 @@ func (mp4 *MP4) AddSampleToTrack(trackID uint32, isKeyframe bool, data []byte, p func (mp4 *MP4) Close(config *models.Config) { - // Add the last sample to the track, we will predict the duration based on the last sample - // We are not insert the last sample as we might corrupt playback (as we do not know accurately the next PTS). - // In theory it means we will lose the last sample, so there is millisecond dataloss, but it is better than corrupting playback. - // We could this by using a delayed packet reader, and look for the next PTS (closest one), but that would require a lot of memory and CPU. - - /*duration := uint64(0) - trackID := uint32(1) - if mp4.SampleType == "video" { - duration = mp4.LastVideoSampleDTS - trackID = uint32(mp4.VideoTrack) - } else if mp4.SampleType == "audio" { - duration = 21 //mp4.LastAudioSampleDTS - - } else { - log.Println("mp4.Close(): unknown sample type, cannot calculate duration") - } - - if duration > 0 { - mp4.VideoTotalDuration += duration - mp4.VideoFullSample.DecodeTime = mp4.VideoTotalDuration - duration - mp4.VideoFullSample.Sample.Dur = uint32(duration) - err := mp4.MultiTrackFragment.AddFullSampleToTrack(*mp4.VideoFullSample, trackID) - if err != nil { - } - mp4.Segments = append(mp4.Segments, mp4.Segment) - }*/ - if mp4.VideoTotalDuration == 0 && mp4.AudioTotalDuration == 0 { log.Log.Error("mp4.Close(): no video or audio samples added, cannot create MP4 file") } @@ -296,7 +267,7 @@ func (mp4 *MP4) Close(config *models.Config) { if mp4.Segment != nil { err := mp4.Segment.Encode(mp4.Writer) if err != nil { - panic(err) + log.Log.Error("mp4.Close(): error encoding last segment: " + err.Error()) } } @@ -304,7 +275,7 @@ func (mp4 *MP4) Close(config *models.Config) { defer mp4.FileWriter.Close() // Now we have all the moof and mdat boxes written to the file. - // We can now generate the ftyp and moov boxes, and replace it with the free box we added earlier (size of 10008 bytes). + // We can now generate the ftyp and moov boxes, and replace it with the free box we added earlier (size of 2048 bytes). init := mp4ff.NewMP4Init() // Create a new ftyp box @@ -337,22 +308,21 @@ func (mp4 *MP4) Close(config *models.Config) { init.Moov.AddChild(mvex) // Add a track for the video - if mp4.VideoTrackName == "H264" || mp4.VideoTrackName == "AVC1" { + switch mp4.VideoTrackName { + case "H264", "AVC1": init.AddEmptyTrack(videoTimescale, "video", "und") includePS := true err := init.Moov.Traks[0].SetAVCDescriptor("avc1", mp4.SPSNALUs, mp4.PPSNALUs, includePS) if err != nil { - //panic(err) } init.Moov.Traks[0].Tkhd.Duration = mp4.VideoTotalDuration init.Moov.Traks[0].Mdia.Hdlr.Name = "agent " + utils.VERSION //init.Moov.Traks[0].Mdia.Mdhd.Duration = mp4.VideoTotalDuration - } else if mp4.VideoTrackName == "H265" || mp4.VideoTrackName == "HVC1" { + case "H265", "HVC1": init.AddEmptyTrack(videoTimescale, "video", "und") includePS := true err := init.Moov.Traks[0].SetHEVCDescriptor("hvc1", mp4.VPSNALUs, mp4.SPSNALUs, mp4.PPSNALUs, [][]byte{}, includePS) if err != nil { - //panic(err) } init.Moov.Traks[0].Tkhd.Duration = mp4.VideoTotalDuration init.Moov.Traks[0].Mdia.Hdlr.Name = "agent " + utils.VERSION @@ -372,7 +342,6 @@ func (mp4 *MP4) Close(config *models.Config) { // Set the audio descriptor err := init.Moov.Traks[1].SetAACDescriptor(29, audioSampleRate) if err != nil { - //panic(err) } init.Moov.Traks[1].Tkhd.Duration = mp4.AudioTotalDuration init.Moov.Traks[1].Mdia.Hdlr.Name = "agent " + utils.VERSION @@ -481,16 +450,15 @@ func (mp4 *MP4) Close(config *models.Config) { init.AddChild(sidx)*/ // Get a bit slice writer for the init segment - // Get a byte buffer of 10008 bytes to write the init segment + // Get a byte buffer of FreeBoxSize bytes to write the init segment buffer := bytes.NewBuffer(make([]byte, 0)) init.Encode(buffer) - // The first 10008 bytes of the file is a free box, so we can read it and replace it with the moov box. - // The init box might not be 10008 bytes, so we need to read the first 10008 bytes and then replace it with the moov box. + // The first FreeBoxSize bytes of the file is a free box, so we can read it and replace it with the moov box. + // The init box might not be FreeBoxSize bytes, so we need to read the first FreeBoxSize bytes and then replace it with the moov box. // while the remaining bytes are for a new free box. // Write the init segment at the beginning of the file, replacing the free box if _, err := mp4.FileWriter.WriteAt(buffer.Bytes(), 0); err != nil { - panic(err) } // Calculate the remaining size for the free box @@ -499,10 +467,10 @@ func (mp4 *MP4) Close(config *models.Config) { newFreeBox := mp4ff.NewFreeBox(make([]byte, remainingSize)) var freeBuf bytes.Buffer if err := newFreeBox.Encode(&freeBuf); err != nil { - panic(err) + log.Log.Error("mp4.Close(): error encoding free box: " + err.Error()) } if _, err := mp4.FileWriter.WriteAt(freeBuf.Bytes(), int64(buffer.Len())); err != nil { - panic(err) + log.Log.Error("mp4.Close(): error writing free box: " + err.Error()) } } } @@ -909,7 +877,7 @@ func SampleToAACSampleIndex(sampling int) int { return i } } - panic("not Found AAC Sample Index") + return -1 } func AACSampleIdxToSample(idx int) int {