diff --git a/README.md b/README.md index 54e4487..91d6cbb 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 @@ -71,9 +74,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 | 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 a530e8f..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 @@ -36,11 +37,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 { @@ -107,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 } @@ -125,7 +123,7 @@ func init() { } func newvideostore( - _ context.Context, + ctx context.Context, deps resource.Dependencies, conf resource.Config, logger logging.Logger, @@ -166,6 +164,31 @@ 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. 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 + } + } + 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) + } + + 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) {