From 1e0ea48d3372d6984956b3d53f5e4296c56fdadc Mon Sep 17 00:00:00 2001 From: hexbabe Date: Wed, 18 Dec 2024 15:09:22 -0500 Subject: [PATCH 1/3] Init changes --- README.md | 6 +++--- cam/cam.go | 33 +++++++++++++++++++++++++++------ 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 54e4487..58f60a2 100644 --- a/README.md +++ b/README.md @@ -71,9 +71,9 @@ On the new component panel, copy and paste the following attribute template into | | `bitrate` | integer | optional | Throughput of encoder in bits per second. Higher for better quality video, and lower for better storage efficiency. | | | `preset` | string | optional | Name of codec video preset to use. See [here](https://trac.ffmpeg.org/wiki/Encode/H.264#a2.Chooseapresetandtune) for preset options. | | `cam_props` | | object | required | | -| | `width` | integer | required | Width of the source camera frames in pixels. | -| | `height` | integer | required | Height of the source camera frames in pixels. | -| | `framerate` | integer | required | Number of frames per second provided by the source camera. | +| | `width` | integer | optional | Width of the source camera frames in pixels. If unspecified, will try to autodetect by fetching a frame from the source camera. | +| | `height` | integer | optional | Height of the source camera frames in pixels. If unspecified, will try to autodetect by fetching a frame from the source camera. | +| | `framerate` | integer | required | Number of frames per second provided by the source camera. Unlike width and height, this is a required attribute, and cannot be autodetected. | ### Example Configuration diff --git a/cam/cam.go b/cam/cam.go index a530e8f..4cf5303 100644 --- a/cam/cam.go +++ b/cam/cam.go @@ -36,11 +36,12 @@ const ( defaultStoragePath = ".viam/video-storage" defaultLogLevel = "error" - maxGRPCSize = 1024 * 1024 * 32 // bytes - deleterInterval = 10 // minutes - retryInterval = 1 // seconds - asyncTimeout = 60 // seconds - tempPath = "/tmp" + maxGRPCSize = 1024 * 1024 * 32 // bytes + deleterInterval = 10 // minutes + retryInterval = 1 // seconds + asyncTimeout = 60 // seconds + numFetchFrameAttempts = 3 // iterations + tempPath = "/tmp" ) type videostore struct { @@ -125,7 +126,7 @@ func init() { } func newvideostore( - _ context.Context, + ctx context.Context, deps resource.Dependencies, conf resource.Config, logger logging.Logger, @@ -166,6 +167,26 @@ func newvideostore( if newConf.Video.Format != "" { format = newConf.Video.Format } + + if newConf.Properties.Width == 0 && newConf.Properties.Height == 0 { + vs.logger.Info("received unspecified frame width and height, fetching frame to get dimensions") + for range make([]struct{}, numFetchFrameAttempts) { + frame, err := camera.DecodeImageFromCamera(ctx, rutils.MimeTypeJPEG, nil, vs.cam) + if err != nil { + vs.logger.Warn("failed to get and decode frame from camera, retrying...", err) + time.Sleep(retryInterval * time.Second) + continue + } + bounds := frame.Bounds() + newConf.Properties.Width = bounds.Dx() + newConf.Properties.Height = bounds.Dy() + break + } + } + if newConf.Properties.Width == 0 && newConf.Properties.Height == 0 { + return nil, fmt.Errorf("failed to get source camera width and height after %d attempts", numFetchFrameAttempts) + } + vs.enc, err = newEncoder( logger, codec, From 01dabec777ccdb3a6ac596bd8b2a5588756821a1 Mon Sep 17 00:00:00 2001 From: hexbabe Date: Wed, 18 Dec 2024 15:46:22 -0500 Subject: [PATCH 2/3] Make logs better; Update thing that tripped me up in README --- README.md | 3 +++ cam/cam.go | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 58f60a2..c64dbcc 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,9 @@ On the new component panel, copy and paste the following attribute template into } } ``` + +Additionally, make sure to add your configured data manager service to the `depends_on` array of your `video-store` component. + > For more information, see [Configure a Machine](https://docs.viam.com/manage/configuration/). ### Attributes diff --git a/cam/cam.go b/cam/cam.go index 4cf5303..f9ee54c 100644 --- a/cam/cam.go +++ b/cam/cam.go @@ -173,13 +173,14 @@ func newvideostore( for range make([]struct{}, numFetchFrameAttempts) { frame, err := camera.DecodeImageFromCamera(ctx, rutils.MimeTypeJPEG, nil, vs.cam) if err != nil { - vs.logger.Warn("failed to get and decode frame from camera, retrying...", err) + vs.logger.Warn("failed to get and decode frame from camera, retrying. Error: ", err) time.Sleep(retryInterval * time.Second) continue } bounds := frame.Bounds() newConf.Properties.Width = bounds.Dx() newConf.Properties.Height = bounds.Dy() + vs.logger.Infof("received frame width and height: %d, %d", newConf.Properties.Width, newConf.Properties.Height) break } } From cb65d5928ef38e02b98886b0919b13350f6d4e4e Mon Sep 17 00:00:00 2001 From: hexbabe Date: Thu, 19 Dec 2024 09:58:40 -0800 Subject: [PATCH 3/3] Make framerate optional and default to 20; Remove validation case for empty cam props; Modify camp props test --- README.md | 2 +- cam/cam.go | 9 +++++---- tests/config_test.go | 8 +++----- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index c64dbcc..91d6cbb 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ Additionally, make sure to add your configured data manager service to the `depe | `cam_props` | | object | required | | | | `width` | integer | optional | Width of the source camera frames in pixels. If unspecified, will try to autodetect by fetching a frame from the source camera. | | | `height` | integer | optional | Height of the source camera frames in pixels. If unspecified, will try to autodetect by fetching a frame from the source camera. | -| | `framerate` | integer | required | Number of frames per second provided by the source camera. Unlike width and height, this is a required attribute, and cannot be autodetected. | +| | `framerate` | integer | optional | Number of frames per second provided by the source camera. Default is 20. | ### Example Configuration diff --git a/cam/cam.go b/cam/cam.go index f9ee54c..a0c0f70 100644 --- a/cam/cam.go +++ b/cam/cam.go @@ -26,6 +26,7 @@ var Model = resource.ModelNamespace("viam").WithFamily("video").WithModel("stora const ( // Default values for the video storage camera component. + defaultFramerate = 20 // frames per second defaultSegmentSeconds = 30 // seconds defaultStorageSize = 10 // GB defaultVideoCodec = codecH264 @@ -108,10 +109,6 @@ func (cfg *Config) Validate(path string) ([]string, error) { if cfg.Sync == "" { return nil, utils.NewConfigValidationFieldRequiredError(path, "sync") } - // TODO(seanp): Remove once camera properties are returned from camera component. - if cfg.Properties == (cameraProperties{}) { - return nil, utils.NewConfigValidationFieldRequiredError(path, "cam_props") - } return []string{cfg.Camera}, nil } @@ -188,6 +185,10 @@ func newvideostore( return nil, fmt.Errorf("failed to get source camera width and height after %d attempts", numFetchFrameAttempts) } + if newConf.Properties.Framerate == 0 { + newConf.Properties.Framerate = defaultFramerate + } + vs.enc, err = newEncoder( logger, codec, diff --git a/tests/config_test.go b/tests/config_test.go index d079e80..faca75b 100644 --- a/tests/config_test.go +++ b/tests/config_test.go @@ -483,16 +483,14 @@ func TestModuleConfiguration(t *testing.T) { test.That(t, err.Error(), test.ShouldContainSubstring, "size_gb") }) - t.Run("Fails Configuration No CamProps", func(t *testing.T) { + t.Run("No CamProps succeeds with defaults", func(t *testing.T) { timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() r, err := setupViamServer(timeoutCtx, config5) test.That(t, err, test.ShouldBeNil) defer r.Close(timeoutCtx) - cam, err := camera.FromRobot(r, videoStoreComponentName) - test.That(t, err, test.ShouldNotBeNil) - test.That(t, cam, test.ShouldBeNil) - test.That(t, err.Error(), test.ShouldContainSubstring, "cam_props") + _, err = camera.FromRobot(r, videoStoreComponentName) + test.That(t, err, test.ShouldBeNil) }) t.Run("Fails Configuration No DataManager", func(t *testing.T) {