Add or update .codecov copy.yml
This commit is contained in:
@@ -0,0 +1,391 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Media service SOAP message types
|
||||
|
||||
// GetProfilesResponse represents GetProfiles response.
|
||||
type GetProfilesResponse struct {
|
||||
XMLName xml.Name `xml:"http://www.onvif.org/ver10/media/wsdl GetProfilesResponse"`
|
||||
Profiles []MediaProfile `xml:"Profiles"`
|
||||
}
|
||||
|
||||
// MediaProfile represents a media profile.
|
||||
type MediaProfile struct {
|
||||
Token string `xml:"token,attr"`
|
||||
Fixed bool `xml:"fixed,attr"`
|
||||
Name string `xml:"Name"`
|
||||
VideoSourceConfiguration *VideoSourceConfiguration `xml:"VideoSourceConfiguration"`
|
||||
AudioSourceConfiguration *AudioSourceConfiguration `xml:"AudioSourceConfiguration,omitempty"`
|
||||
VideoEncoderConfiguration *VideoEncoderConfiguration `xml:"VideoEncoderConfiguration"`
|
||||
AudioEncoderConfiguration *AudioEncoderConfiguration `xml:"AudioEncoderConfiguration,omitempty"`
|
||||
VideoAnalyticsConfiguration *VideoAnalyticsConfiguration `xml:"VideoAnalyticsConfiguration,omitempty"`
|
||||
PTZConfiguration *PTZConfiguration `xml:"PTZConfiguration,omitempty"`
|
||||
MetadataConfiguration *MetadataConfiguration `xml:"MetadataConfiguration,omitempty"`
|
||||
}
|
||||
|
||||
// VideoSourceConfiguration represents video source configuration.
|
||||
type VideoSourceConfiguration struct {
|
||||
Token string `xml:"token,attr"`
|
||||
Name string `xml:"Name"`
|
||||
UseCount int `xml:"UseCount"`
|
||||
SourceToken string `xml:"SourceToken"`
|
||||
Bounds IntRectangle `xml:"Bounds"`
|
||||
}
|
||||
|
||||
// AudioSourceConfiguration represents audio source configuration.
|
||||
type AudioSourceConfiguration struct {
|
||||
Token string `xml:"token,attr"`
|
||||
Name string `xml:"Name"`
|
||||
UseCount int `xml:"UseCount"`
|
||||
SourceToken string `xml:"SourceToken"`
|
||||
}
|
||||
|
||||
// VideoEncoderConfiguration represents video encoder configuration.
|
||||
type VideoEncoderConfiguration struct {
|
||||
Token string `xml:"token,attr"`
|
||||
Name string `xml:"Name"`
|
||||
UseCount int `xml:"UseCount"`
|
||||
Encoding string `xml:"Encoding"`
|
||||
Resolution VideoResolution `xml:"Resolution"`
|
||||
Quality float64 `xml:"Quality"`
|
||||
RateControl *VideoRateControl `xml:"RateControl,omitempty"`
|
||||
H264 *H264Configuration `xml:"H264,omitempty"`
|
||||
Multicast *MulticastConfiguration `xml:"Multicast,omitempty"`
|
||||
SessionTimeout string `xml:"SessionTimeout"`
|
||||
}
|
||||
|
||||
// AudioEncoderConfiguration represents audio encoder configuration.
|
||||
type AudioEncoderConfiguration struct {
|
||||
Token string `xml:"token,attr"`
|
||||
Name string `xml:"Name"`
|
||||
UseCount int `xml:"UseCount"`
|
||||
Encoding string `xml:"Encoding"`
|
||||
Bitrate int `xml:"Bitrate"`
|
||||
SampleRate int `xml:"SampleRate"`
|
||||
Multicast *MulticastConfiguration `xml:"Multicast,omitempty"`
|
||||
SessionTimeout string `xml:"SessionTimeout"`
|
||||
}
|
||||
|
||||
// VideoAnalyticsConfiguration represents video analytics configuration.
|
||||
type VideoAnalyticsConfiguration struct {
|
||||
Token string `xml:"token,attr"`
|
||||
Name string `xml:"Name"`
|
||||
UseCount int `xml:"UseCount"`
|
||||
}
|
||||
|
||||
// PTZConfiguration represents PTZ configuration.
|
||||
type PTZConfiguration struct {
|
||||
Token string `xml:"token,attr"`
|
||||
Name string `xml:"Name"`
|
||||
UseCount int `xml:"UseCount"`
|
||||
NodeToken string `xml:"NodeToken"`
|
||||
}
|
||||
|
||||
// MetadataConfiguration represents metadata configuration.
|
||||
type MetadataConfiguration struct {
|
||||
Token string `xml:"token,attr"`
|
||||
Name string `xml:"Name"`
|
||||
UseCount int `xml:"UseCount"`
|
||||
SessionTimeout string `xml:"SessionTimeout"`
|
||||
}
|
||||
|
||||
// IntRectangle represents a rectangle with integer coordinates.
|
||||
type IntRectangle struct {
|
||||
X int `xml:"x,attr"`
|
||||
Y int `xml:"y,attr"`
|
||||
Width int `xml:"width,attr"`
|
||||
Height int `xml:"height,attr"`
|
||||
}
|
||||
|
||||
// VideoResolution represents video resolution.
|
||||
type VideoResolution struct {
|
||||
Width int `xml:"Width"`
|
||||
Height int `xml:"Height"`
|
||||
}
|
||||
|
||||
// VideoRateControl represents video rate control.
|
||||
type VideoRateControl struct {
|
||||
FrameRateLimit int `xml:"FrameRateLimit"`
|
||||
EncodingInterval int `xml:"EncodingInterval"`
|
||||
BitrateLimit int `xml:"BitrateLimit"`
|
||||
}
|
||||
|
||||
// H264Configuration represents H264 configuration.
|
||||
type H264Configuration struct {
|
||||
GovLength int `xml:"GovLength"`
|
||||
H264Profile string `xml:"H264Profile"`
|
||||
}
|
||||
|
||||
// MulticastConfiguration represents multicast configuration.
|
||||
type MulticastConfiguration struct {
|
||||
Address IPAddress `xml:"Address"`
|
||||
Port int `xml:"Port"`
|
||||
TTL int `xml:"TTL"`
|
||||
AutoStart bool `xml:"AutoStart"`
|
||||
}
|
||||
|
||||
// IPAddress represents an IP address.
|
||||
type IPAddress struct {
|
||||
Type string `xml:"Type"`
|
||||
IPv4Address string `xml:"IPv4Address,omitempty"`
|
||||
IPv6Address string `xml:"IPv6Address,omitempty"`
|
||||
}
|
||||
|
||||
// GetStreamURIResponse represents GetStreamURI response.
|
||||
type GetStreamURIResponse struct {
|
||||
XMLName xml.Name `xml:"http://www.onvif.org/ver10/media/wsdl GetStreamURIResponse"`
|
||||
MediaURI MediaURI `xml:"MediaUri"`
|
||||
}
|
||||
|
||||
// MediaURI represents a media URI.
|
||||
type MediaURI struct {
|
||||
URI string `xml:"Uri"`
|
||||
InvalidAfterConnect bool `xml:"InvalidAfterConnect"`
|
||||
InvalidAfterReboot bool `xml:"InvalidAfterReboot"`
|
||||
Timeout string `xml:"Timeout"`
|
||||
}
|
||||
|
||||
// GetSnapshotURIResponse represents GetSnapshotURI response.
|
||||
type GetSnapshotURIResponse struct {
|
||||
XMLName xml.Name `xml:"http://www.onvif.org/ver10/media/wsdl GetSnapshotURIResponse"`
|
||||
MediaURI MediaURI `xml:"MediaUri"`
|
||||
}
|
||||
|
||||
// GetVideoSourcesResponse represents GetVideoSources response.
|
||||
type GetVideoSourcesResponse struct {
|
||||
XMLName xml.Name `xml:"http://www.onvif.org/ver10/media/wsdl GetVideoSourcesResponse"`
|
||||
VideoSources []VideoSource `xml:"VideoSources"`
|
||||
}
|
||||
|
||||
// VideoSource represents a video source.
|
||||
type VideoSource struct {
|
||||
Token string `xml:"token,attr"`
|
||||
Framerate float64 `xml:"Framerate"`
|
||||
Resolution VideoResolution `xml:"Resolution"`
|
||||
}
|
||||
|
||||
// Media service handlers
|
||||
|
||||
// HandleGetProfiles handles GetProfiles request.
|
||||
func (s *Server) HandleGetProfiles(body interface{}) (interface{}, error) {
|
||||
profiles := make([]MediaProfile, len(s.config.Profiles))
|
||||
|
||||
//nolint:gocritic // Range value copy is acceptable for small structs
|
||||
for i, profileCfg := range s.config.Profiles {
|
||||
profile := MediaProfile{
|
||||
Token: profileCfg.Token,
|
||||
Fixed: true,
|
||||
Name: profileCfg.Name,
|
||||
VideoSourceConfiguration: &VideoSourceConfiguration{
|
||||
Token: profileCfg.VideoSource.Token,
|
||||
Name: profileCfg.VideoSource.Name,
|
||||
UseCount: 1,
|
||||
SourceToken: profileCfg.VideoSource.Token,
|
||||
Bounds: IntRectangle{
|
||||
X: profileCfg.VideoSource.Bounds.X,
|
||||
Y: profileCfg.VideoSource.Bounds.Y,
|
||||
Width: profileCfg.VideoSource.Bounds.Width,
|
||||
Height: profileCfg.VideoSource.Bounds.Height,
|
||||
},
|
||||
},
|
||||
VideoEncoderConfiguration: &VideoEncoderConfiguration{
|
||||
Token: profileCfg.Token + "_encoder",
|
||||
Name: profileCfg.Name + " Encoder",
|
||||
UseCount: 1,
|
||||
Encoding: profileCfg.VideoEncoder.Encoding,
|
||||
Resolution: VideoResolution{
|
||||
Width: profileCfg.VideoEncoder.Resolution.Width,
|
||||
Height: profileCfg.VideoEncoder.Resolution.Height,
|
||||
},
|
||||
Quality: profileCfg.VideoEncoder.Quality,
|
||||
RateControl: &VideoRateControl{
|
||||
FrameRateLimit: profileCfg.VideoEncoder.Framerate,
|
||||
EncodingInterval: 1,
|
||||
BitrateLimit: profileCfg.VideoEncoder.Bitrate,
|
||||
},
|
||||
SessionTimeout: "PT60S",
|
||||
},
|
||||
}
|
||||
|
||||
// Add H264 configuration if encoding is H264
|
||||
if profileCfg.VideoEncoder.Encoding == "H264" {
|
||||
profile.VideoEncoderConfiguration.H264 = &H264Configuration{
|
||||
GovLength: profileCfg.VideoEncoder.GovLength,
|
||||
H264Profile: "Main",
|
||||
}
|
||||
}
|
||||
|
||||
// Add audio configuration if present
|
||||
if profileCfg.AudioSource != nil {
|
||||
profile.AudioSourceConfiguration = &AudioSourceConfiguration{
|
||||
Token: profileCfg.AudioSource.Token,
|
||||
Name: profileCfg.AudioSource.Name,
|
||||
UseCount: 1,
|
||||
SourceToken: profileCfg.AudioSource.Token,
|
||||
}
|
||||
}
|
||||
|
||||
if profileCfg.AudioEncoder != nil {
|
||||
profile.AudioEncoderConfiguration = &AudioEncoderConfiguration{
|
||||
Token: profileCfg.Token + "_audio_encoder",
|
||||
Name: profileCfg.Name + " Audio Encoder",
|
||||
UseCount: 1,
|
||||
Encoding: profileCfg.AudioEncoder.Encoding,
|
||||
Bitrate: profileCfg.AudioEncoder.Bitrate,
|
||||
SampleRate: profileCfg.AudioEncoder.SampleRate,
|
||||
SessionTimeout: "PT60S",
|
||||
}
|
||||
}
|
||||
|
||||
// Add PTZ configuration if present
|
||||
if profileCfg.PTZ != nil {
|
||||
profile.PTZConfiguration = &PTZConfiguration{
|
||||
Token: profileCfg.PTZ.NodeToken,
|
||||
Name: profileCfg.Name + " PTZ",
|
||||
UseCount: 1,
|
||||
NodeToken: profileCfg.PTZ.NodeToken,
|
||||
}
|
||||
}
|
||||
|
||||
profiles[i] = profile
|
||||
}
|
||||
|
||||
return &GetProfilesResponse{
|
||||
Profiles: profiles,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// HandleGetStreamURI handles GetStreamURI request.
|
||||
func (s *Server) HandleGetStreamURI(body interface{}) (interface{}, error) {
|
||||
var req struct {
|
||||
ProfileToken string `xml:"ProfileToken"`
|
||||
}
|
||||
|
||||
if err := unmarshalBody(body, &req); err != nil {
|
||||
return nil, fmt.Errorf("invalid request: %w", err)
|
||||
}
|
||||
|
||||
// Find the stream configuration for this profile
|
||||
streamCfg, ok := s.streams[req.ProfileToken]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%w: %s", ErrProfileNotFound, req.ProfileToken)
|
||||
}
|
||||
|
||||
// Build RTSP URI
|
||||
uri := streamCfg.StreamURI
|
||||
if uri == "" {
|
||||
// Default URI construction
|
||||
host := s.config.Host
|
||||
if host == defaultHost || host == "" {
|
||||
host = defaultHostname
|
||||
}
|
||||
uri = fmt.Sprintf("rtsp://%s:8554%s", host, streamCfg.RTSPPath)
|
||||
}
|
||||
|
||||
return &GetStreamURIResponse{
|
||||
MediaURI: MediaURI{
|
||||
URI: uri,
|
||||
InvalidAfterConnect: false,
|
||||
InvalidAfterReboot: true,
|
||||
Timeout: "PT60S",
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// HandleGetSnapshotURI handles GetSnapshotURI request.
|
||||
func (s *Server) HandleGetSnapshotURI(body interface{}) (interface{}, error) {
|
||||
var req struct {
|
||||
ProfileToken string `xml:"ProfileToken"`
|
||||
}
|
||||
|
||||
if err := unmarshalBody(body, &req); err != nil {
|
||||
return nil, fmt.Errorf("invalid request: %w", err)
|
||||
}
|
||||
|
||||
// Find the profile
|
||||
var profileCfg *ProfileConfig
|
||||
for i := range s.config.Profiles {
|
||||
if s.config.Profiles[i].Token == req.ProfileToken {
|
||||
profileCfg = &s.config.Profiles[i]
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if profileCfg == nil {
|
||||
return nil, fmt.Errorf("%w: %s", ErrProfileNotFound, req.ProfileToken)
|
||||
}
|
||||
|
||||
if !profileCfg.Snapshot.Enabled {
|
||||
return nil, fmt.Errorf("%w: %s", ErrSnapshotNotSupported, req.ProfileToken)
|
||||
}
|
||||
|
||||
// Build snapshot URI
|
||||
host := s.config.Host
|
||||
if host == defaultHost || host == "" {
|
||||
host = defaultHostname
|
||||
}
|
||||
uri := fmt.Sprintf("http://%s:%d%s/snapshot?profile=%s",
|
||||
host, s.config.Port, s.config.BasePath, req.ProfileToken)
|
||||
|
||||
return &GetSnapshotURIResponse{
|
||||
MediaURI: MediaURI{
|
||||
URI: uri,
|
||||
InvalidAfterConnect: false,
|
||||
InvalidAfterReboot: true,
|
||||
Timeout: "PT5S",
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// HandleGetVideoSources handles GetVideoSources request.
|
||||
func (s *Server) HandleGetVideoSources(body interface{}) (interface{}, error) {
|
||||
sources := make([]VideoSource, 0)
|
||||
|
||||
// Collect unique video sources from profiles
|
||||
seenSources := make(map[string]bool)
|
||||
//nolint:gocritic // Range value copy is acceptable for small structs
|
||||
for _, profileCfg := range s.config.Profiles {
|
||||
if !seenSources[profileCfg.VideoSource.Token] {
|
||||
sources = append(sources, VideoSource{
|
||||
Token: profileCfg.VideoSource.Token,
|
||||
Framerate: float64(profileCfg.VideoSource.Framerate),
|
||||
Resolution: VideoResolution{
|
||||
Width: profileCfg.VideoSource.Resolution.Width,
|
||||
Height: profileCfg.VideoSource.Resolution.Height,
|
||||
},
|
||||
})
|
||||
seenSources[profileCfg.VideoSource.Token] = true
|
||||
}
|
||||
}
|
||||
|
||||
return &GetVideoSourcesResponse{
|
||||
VideoSources: sources,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// unmarshalBody is a helper to unmarshal SOAP body content.
|
||||
func unmarshalBody(body, target interface{}) error {
|
||||
var bodyXML []byte
|
||||
var err error
|
||||
|
||||
// If body is already []byte, use it directly
|
||||
if b, ok := body.([]byte); ok {
|
||||
bodyXML = b
|
||||
} else {
|
||||
bodyXML, err = xml.Marshal(body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal XML: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := xml.Unmarshal(bodyXML, target); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal XML: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user