package onvif import ( "context" "encoding/xml" "fmt" "github.com/0x524A/onvif-go/soap" ) // Imaging service namespace const imagingNamespace = "http://www.onvif.org/ver20/imaging/wsdl" // GetImagingSettings retrieves imaging settings for a video source func (c *Client) GetImagingSettings(ctx context.Context, videoSourceToken string) (*ImagingSettings, error) { endpoint := c.imagingEndpoint if endpoint == "" { endpoint = c.endpoint } type GetImagingSettings struct { XMLName xml.Name `xml:"timg:GetImagingSettings"` Xmlns string `xml:"xmlns:timg,attr"` VideoSourceToken string `xml:"timg:VideoSourceToken"` } type GetImagingSettingsResponse struct { XMLName xml.Name `xml:"GetImagingSettingsResponse"` ImagingSettings struct { BacklightCompensation *struct { Mode string `xml:"Mode"` Level float64 `xml:"Level"` } `xml:"BacklightCompensation"` Brightness *float64 `xml:"Brightness"` ColorSaturation *float64 `xml:"ColorSaturation"` Contrast *float64 `xml:"Contrast"` Exposure *struct { Mode string `xml:"Mode"` Priority string `xml:"Priority"` MinExposureTime float64 `xml:"MinExposureTime"` MaxExposureTime float64 `xml:"MaxExposureTime"` MinGain float64 `xml:"MinGain"` MaxGain float64 `xml:"MaxGain"` MinIris float64 `xml:"MinIris"` MaxIris float64 `xml:"MaxIris"` ExposureTime float64 `xml:"ExposureTime"` Gain float64 `xml:"Gain"` Iris float64 `xml:"Iris"` } `xml:"Exposure"` Focus *struct { AutoFocusMode string `xml:"AutoFocusMode"` DefaultSpeed float64 `xml:"DefaultSpeed"` NearLimit float64 `xml:"NearLimit"` FarLimit float64 `xml:"FarLimit"` } `xml:"Focus"` IrCutFilter *string `xml:"IrCutFilter"` Sharpness *float64 `xml:"Sharpness"` WideDynamicRange *struct { Mode string `xml:"Mode"` Level float64 `xml:"Level"` } `xml:"WideDynamicRange"` WhiteBalance *struct { Mode string `xml:"Mode"` CrGain float64 `xml:"CrGain"` CbGain float64 `xml:"CbGain"` } `xml:"WhiteBalance"` } `xml:"ImagingSettings"` } req := GetImagingSettings{ Xmlns: imagingNamespace, VideoSourceToken: videoSourceToken, } var resp GetImagingSettingsResponse username, password := c.GetCredentials() soapClient := soap.NewClient(c.httpClient, username, password) if err := soapClient.Call(ctx, endpoint, "", req, &resp); err != nil { return nil, fmt.Errorf("GetImagingSettings failed: %w", err) } settings := &ImagingSettings{ Brightness: resp.ImagingSettings.Brightness, ColorSaturation: resp.ImagingSettings.ColorSaturation, Contrast: resp.ImagingSettings.Contrast, IrCutFilter: resp.ImagingSettings.IrCutFilter, Sharpness: resp.ImagingSettings.Sharpness, } if resp.ImagingSettings.BacklightCompensation != nil { settings.BacklightCompensation = &BacklightCompensation{ Mode: resp.ImagingSettings.BacklightCompensation.Mode, Level: resp.ImagingSettings.BacklightCompensation.Level, } } if resp.ImagingSettings.Exposure != nil { settings.Exposure = &Exposure{ Mode: resp.ImagingSettings.Exposure.Mode, Priority: resp.ImagingSettings.Exposure.Priority, MinExposureTime: resp.ImagingSettings.Exposure.MinExposureTime, MaxExposureTime: resp.ImagingSettings.Exposure.MaxExposureTime, MinGain: resp.ImagingSettings.Exposure.MinGain, MaxGain: resp.ImagingSettings.Exposure.MaxGain, MinIris: resp.ImagingSettings.Exposure.MinIris, MaxIris: resp.ImagingSettings.Exposure.MaxIris, ExposureTime: resp.ImagingSettings.Exposure.ExposureTime, Gain: resp.ImagingSettings.Exposure.Gain, Iris: resp.ImagingSettings.Exposure.Iris, } } if resp.ImagingSettings.Focus != nil { settings.Focus = &FocusConfiguration{ AutoFocusMode: resp.ImagingSettings.Focus.AutoFocusMode, DefaultSpeed: resp.ImagingSettings.Focus.DefaultSpeed, NearLimit: resp.ImagingSettings.Focus.NearLimit, FarLimit: resp.ImagingSettings.Focus.FarLimit, } } if resp.ImagingSettings.WideDynamicRange != nil { settings.WideDynamicRange = &WideDynamicRange{ Mode: resp.ImagingSettings.WideDynamicRange.Mode, Level: resp.ImagingSettings.WideDynamicRange.Level, } } if resp.ImagingSettings.WhiteBalance != nil { settings.WhiteBalance = &WhiteBalance{ Mode: resp.ImagingSettings.WhiteBalance.Mode, CrGain: resp.ImagingSettings.WhiteBalance.CrGain, CbGain: resp.ImagingSettings.WhiteBalance.CbGain, } } return settings, nil } // SetImagingSettings sets imaging settings for a video source func (c *Client) SetImagingSettings(ctx context.Context, videoSourceToken string, settings *ImagingSettings, forcePersistence bool) error { endpoint := c.imagingEndpoint if endpoint == "" { endpoint = c.endpoint } type SetImagingSettings struct { XMLName xml.Name `xml:"timg:SetImagingSettings"` Xmlns string `xml:"xmlns:timg,attr"` VideoSourceToken string `xml:"timg:VideoSourceToken"` ImagingSettings struct { BacklightCompensation *struct { Mode string `xml:"Mode"` Level float64 `xml:"Level"` } `xml:"BacklightCompensation,omitempty"` Brightness *float64 `xml:"Brightness,omitempty"` ColorSaturation *float64 `xml:"ColorSaturation,omitempty"` Contrast *float64 `xml:"Contrast,omitempty"` Exposure *struct { Mode string `xml:"Mode"` Priority string `xml:"Priority,omitempty"` MinExposureTime float64 `xml:"MinExposureTime,omitempty"` MaxExposureTime float64 `xml:"MaxExposureTime,omitempty"` MinGain float64 `xml:"MinGain,omitempty"` MaxGain float64 `xml:"MaxGain,omitempty"` MinIris float64 `xml:"MinIris,omitempty"` MaxIris float64 `xml:"MaxIris,omitempty"` ExposureTime float64 `xml:"ExposureTime,omitempty"` Gain float64 `xml:"Gain,omitempty"` Iris float64 `xml:"Iris,omitempty"` } `xml:"Exposure,omitempty"` Focus *struct { AutoFocusMode string `xml:"AutoFocusMode"` DefaultSpeed float64 `xml:"DefaultSpeed,omitempty"` NearLimit float64 `xml:"NearLimit,omitempty"` FarLimit float64 `xml:"FarLimit,omitempty"` } `xml:"Focus,omitempty"` IrCutFilter *string `xml:"IrCutFilter,omitempty"` Sharpness *float64 `xml:"Sharpness,omitempty"` WideDynamicRange *struct { Mode string `xml:"Mode"` Level float64 `xml:"Level,omitempty"` } `xml:"WideDynamicRange,omitempty"` WhiteBalance *struct { Mode string `xml:"Mode"` CrGain float64 `xml:"CrGain,omitempty"` CbGain float64 `xml:"CbGain,omitempty"` } `xml:"WhiteBalance,omitempty"` } `xml:"timg:ImagingSettings"` ForcePersistence bool `xml:"timg:ForcePersistence"` } req := SetImagingSettings{ Xmlns: imagingNamespace, VideoSourceToken: videoSourceToken, ForcePersistence: forcePersistence, } // Map settings if settings.BacklightCompensation != nil { req.ImagingSettings.BacklightCompensation = &struct { Mode string `xml:"Mode"` Level float64 `xml:"Level"` }{ Mode: settings.BacklightCompensation.Mode, Level: settings.BacklightCompensation.Level, } } req.ImagingSettings.Brightness = settings.Brightness req.ImagingSettings.ColorSaturation = settings.ColorSaturation req.ImagingSettings.Contrast = settings.Contrast req.ImagingSettings.IrCutFilter = settings.IrCutFilter req.ImagingSettings.Sharpness = settings.Sharpness if settings.Exposure != nil { req.ImagingSettings.Exposure = &struct { Mode string `xml:"Mode"` Priority string `xml:"Priority,omitempty"` MinExposureTime float64 `xml:"MinExposureTime,omitempty"` MaxExposureTime float64 `xml:"MaxExposureTime,omitempty"` MinGain float64 `xml:"MinGain,omitempty"` MaxGain float64 `xml:"MaxGain,omitempty"` MinIris float64 `xml:"MinIris,omitempty"` MaxIris float64 `xml:"MaxIris,omitempty"` ExposureTime float64 `xml:"ExposureTime,omitempty"` Gain float64 `xml:"Gain,omitempty"` Iris float64 `xml:"Iris,omitempty"` }{ Mode: settings.Exposure.Mode, Priority: settings.Exposure.Priority, MinExposureTime: settings.Exposure.MinExposureTime, MaxExposureTime: settings.Exposure.MaxExposureTime, MinGain: settings.Exposure.MinGain, MaxGain: settings.Exposure.MaxGain, MinIris: settings.Exposure.MinIris, MaxIris: settings.Exposure.MaxIris, ExposureTime: settings.Exposure.ExposureTime, Gain: settings.Exposure.Gain, Iris: settings.Exposure.Iris, } } if settings.Focus != nil { req.ImagingSettings.Focus = &struct { AutoFocusMode string `xml:"AutoFocusMode"` DefaultSpeed float64 `xml:"DefaultSpeed,omitempty"` NearLimit float64 `xml:"NearLimit,omitempty"` FarLimit float64 `xml:"FarLimit,omitempty"` }{ AutoFocusMode: settings.Focus.AutoFocusMode, DefaultSpeed: settings.Focus.DefaultSpeed, NearLimit: settings.Focus.NearLimit, FarLimit: settings.Focus.FarLimit, } } if settings.WideDynamicRange != nil { req.ImagingSettings.WideDynamicRange = &struct { Mode string `xml:"Mode"` Level float64 `xml:"Level,omitempty"` }{ Mode: settings.WideDynamicRange.Mode, Level: settings.WideDynamicRange.Level, } } if settings.WhiteBalance != nil { req.ImagingSettings.WhiteBalance = &struct { Mode string `xml:"Mode"` CrGain float64 `xml:"CrGain,omitempty"` CbGain float64 `xml:"CbGain,omitempty"` }{ Mode: settings.WhiteBalance.Mode, CrGain: settings.WhiteBalance.CrGain, CbGain: settings.WhiteBalance.CbGain, } } username, password := c.GetCredentials() soapClient := soap.NewClient(c.httpClient, username, password) if err := soapClient.Call(ctx, endpoint, "", req, nil); err != nil { return fmt.Errorf("SetImagingSettings failed: %w", err) } return nil } // Move performs a focus move operation func (c *Client) Move(ctx context.Context, videoSourceToken string, focus *FocusMove) error { endpoint := c.imagingEndpoint if endpoint == "" { endpoint = c.endpoint } type Move struct { XMLName xml.Name `xml:"timg:Move"` Xmlns string `xml:"xmlns:timg,attr"` VideoSourceToken string `xml:"timg:VideoSourceToken"` Focus *struct { Absolute *struct { Position float64 `xml:"Position"` Speed float64 `xml:"Speed,omitempty"` } `xml:"Absolute,omitempty"` Relative *struct { Distance float64 `xml:"Distance"` Speed float64 `xml:"Speed,omitempty"` } `xml:"Relative,omitempty"` Continuous *struct { Speed float64 `xml:"Speed"` } `xml:"Continuous,omitempty"` } `xml:"timg:Focus"` } req := Move{ Xmlns: imagingNamespace, VideoSourceToken: videoSourceToken, } if focus != nil { req.Focus = &struct { Absolute *struct { Position float64 `xml:"Position"` Speed float64 `xml:"Speed,omitempty"` } `xml:"Absolute,omitempty"` Relative *struct { Distance float64 `xml:"Distance"` Speed float64 `xml:"Speed,omitempty"` } `xml:"Relative,omitempty"` Continuous *struct { Speed float64 `xml:"Speed"` } `xml:"Continuous,omitempty"` }{} // Implementation would add specific focus move types here } username, password := c.GetCredentials() soapClient := soap.NewClient(c.httpClient, username, password) if err := soapClient.Call(ctx, endpoint, "", req, nil); err != nil { return fmt.Errorf("Move failed: %w", err) } return nil } // FocusMove represents a focus move operation (placeholder for focus move types) type FocusMove struct { // Can be extended with Absolute, Relative, Continuous move types } // GetOptions retrieves imaging options for a video source func (c *Client) GetOptions(ctx context.Context, videoSourceToken string) (*ImagingOptions, error) { endpoint := c.imagingEndpoint if endpoint == "" { return nil, ErrServiceNotSupported } type GetOptions struct { XMLName xml.Name `xml:"timg:GetOptions"` Xmlns string `xml:"xmlns:timg,attr"` VideoSourceToken string `xml:"timg:VideoSourceToken"` } type GetOptionsResponse struct { XMLName xml.Name `xml:"GetOptionsResponse"` ImagingOptions struct { BacklightCompensation *struct { Mode []string `xml:"Mode"` Level struct { Min float64 `xml:"Min"` Max float64 `xml:"Max"` } `xml:"Level"` } `xml:"BacklightCompensation"` Brightness *struct { Min float64 `xml:"Min"` Max float64 `xml:"Max"` } `xml:"Brightness"` ColorSaturation *struct { Min float64 `xml:"Min"` Max float64 `xml:"Max"` } `xml:"ColorSaturation"` Contrast *struct { Min float64 `xml:"Min"` Max float64 `xml:"Max"` } `xml:"Contrast"` Exposure *struct { Mode []string `xml:"Mode"` Priority []string `xml:"Priority"` MinExposureTime struct { Min float64 `xml:"Min"` Max float64 `xml:"Max"` } `xml:"MinExposureTime"` MaxExposureTime struct { Min float64 `xml:"Min"` Max float64 `xml:"Max"` } `xml:"MaxExposureTime"` } `xml:"Exposure"` Focus *struct { AutoFocusModes []string `xml:"AutoFocusModes"` DefaultSpeed struct { Min float64 `xml:"Min"` Max float64 `xml:"Max"` } `xml:"DefaultSpeed"` } `xml:"Focus"` } `xml:"ImagingOptions"` } req := GetOptions{ Xmlns: imagingNamespace, VideoSourceToken: videoSourceToken, } var resp GetOptionsResponse username, password := c.GetCredentials() soapClient := soap.NewClient(c.httpClient, username, password) if err := soapClient.Call(ctx, endpoint, "", req, &resp); err != nil { return nil, fmt.Errorf("GetOptions failed: %w", err) } options := &ImagingOptions{} if resp.ImagingOptions.Brightness != nil { options.Brightness = &FloatRange{ Min: resp.ImagingOptions.Brightness.Min, Max: resp.ImagingOptions.Brightness.Max, } } if resp.ImagingOptions.ColorSaturation != nil { options.ColorSaturation = &FloatRange{ Min: resp.ImagingOptions.ColorSaturation.Min, Max: resp.ImagingOptions.ColorSaturation.Max, } } if resp.ImagingOptions.Contrast != nil { options.Contrast = &FloatRange{ Min: resp.ImagingOptions.Contrast.Min, Max: resp.ImagingOptions.Contrast.Max, } } return options, nil } // GetMoveOptions retrieves imaging move options for focus func (c *Client) GetMoveOptions(ctx context.Context, videoSourceToken string) (*MoveOptions, error) { endpoint := c.imagingEndpoint if endpoint == "" { return nil, ErrServiceNotSupported } type GetMoveOptions struct { XMLName xml.Name `xml:"timg:GetMoveOptions"` Xmlns string `xml:"xmlns:timg,attr"` VideoSourceToken string `xml:"timg:VideoSourceToken"` } type GetMoveOptionsResponse struct { XMLName xml.Name `xml:"GetMoveOptionsResponse"` MoveOptions struct { Absolute *struct { Position struct { Min float64 `xml:"Min"` Max float64 `xml:"Max"` } `xml:"Position"` Speed struct { Min float64 `xml:"Min"` Max float64 `xml:"Max"` } `xml:"Speed"` } `xml:"Absolute"` Relative *struct { Distance struct { Min float64 `xml:"Min"` Max float64 `xml:"Max"` } `xml:"Distance"` Speed struct { Min float64 `xml:"Min"` Max float64 `xml:"Max"` } `xml:"Speed"` } `xml:"Relative"` Continuous *struct { Speed struct { Min float64 `xml:"Min"` Max float64 `xml:"Max"` } `xml:"Speed"` } `xml:"Continuous"` } `xml:"MoveOptions"` } req := GetMoveOptions{ Xmlns: imagingNamespace, VideoSourceToken: videoSourceToken, } var resp GetMoveOptionsResponse username, password := c.GetCredentials() soapClient := soap.NewClient(c.httpClient, username, password) if err := soapClient.Call(ctx, endpoint, "", req, &resp); err != nil { return nil, fmt.Errorf("GetMoveOptions failed: %w", err) } options := &MoveOptions{} if resp.MoveOptions.Absolute != nil { options.Absolute = &AbsoluteFocusOptions{ Position: FloatRange{ Min: resp.MoveOptions.Absolute.Position.Min, Max: resp.MoveOptions.Absolute.Position.Max, }, Speed: FloatRange{ Min: resp.MoveOptions.Absolute.Speed.Min, Max: resp.MoveOptions.Absolute.Speed.Max, }, } } if resp.MoveOptions.Relative != nil { options.Relative = &RelativeFocusOptions{ Distance: FloatRange{ Min: resp.MoveOptions.Relative.Distance.Min, Max: resp.MoveOptions.Relative.Distance.Max, }, Speed: FloatRange{ Min: resp.MoveOptions.Relative.Speed.Min, Max: resp.MoveOptions.Relative.Speed.Max, }, } } if resp.MoveOptions.Continuous != nil { options.Continuous = &ContinuousFocusOptions{ Speed: FloatRange{ Min: resp.MoveOptions.Continuous.Speed.Min, Max: resp.MoveOptions.Continuous.Speed.Max, }, } } return options, nil } // StopFocus stops focus movement func (c *Client) StopFocus(ctx context.Context, videoSourceToken string) error { endpoint := c.imagingEndpoint if endpoint == "" { return ErrServiceNotSupported } type Stop struct { XMLName xml.Name `xml:"timg:Stop"` Xmlns string `xml:"xmlns:timg,attr"` VideoSourceToken string `xml:"timg:VideoSourceToken"` } req := Stop{ Xmlns: imagingNamespace, VideoSourceToken: videoSourceToken, } username, password := c.GetCredentials() soapClient := soap.NewClient(c.httpClient, username, password) if err := soapClient.Call(ctx, endpoint, "", req, nil); err != nil { return fmt.Errorf("Stop failed: %w", err) } return nil } // GetImagingStatus retrieves imaging status func (c *Client) GetImagingStatus(ctx context.Context, videoSourceToken string) (*ImagingStatus, error) { endpoint := c.imagingEndpoint if endpoint == "" { return nil, ErrServiceNotSupported } type GetStatus struct { XMLName xml.Name `xml:"timg:GetStatus"` Xmlns string `xml:"xmlns:timg,attr"` VideoSourceToken string `xml:"timg:VideoSourceToken"` } type GetStatusResponse struct { XMLName xml.Name `xml:"GetStatusResponse"` ImagingStatus struct { FocusStatus struct { Position float64 `xml:"Position"` MoveStatus string `xml:"MoveStatus"` Error string `xml:"Error"` } `xml:"FocusStatus"` } `xml:"Status"` } req := GetStatus{ Xmlns: imagingNamespace, VideoSourceToken: videoSourceToken, } var resp GetStatusResponse username, password := c.GetCredentials() soapClient := soap.NewClient(c.httpClient, username, password) if err := soapClient.Call(ctx, endpoint, "", req, &resp); err != nil { return nil, fmt.Errorf("GetStatus failed: %w", err) } return &ImagingStatus{ FocusStatus: &FocusStatus{ Position: resp.ImagingStatus.FocusStatus.Position, MoveStatus: resp.ImagingStatus.FocusStatus.MoveStatus, Error: resp.ImagingStatus.FocusStatus.Error, }, }, nil }