Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions cameras_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,38 @@ func TestUnmarshalXMLCameraSchedule(t *testing.T) {
asert.Equal(3, s.ID, "the data was not unmarshalled properly")
}

// SS 5.5 keeps v5 width/height tags but also emits video-format (a v6-era field).
func TestUnmarshalXMLCameraSS55WithVideoFormat(t *testing.T) {
t.Parallel()

const camXML = `<camera>
<number>1</number>
<connected>yes</connected>
<width>3072</width>
<height>1728</height>
<mode-c>armed</mode-c>
<mode-m>armed</mode-m>
<mode-a>armed</mode-a>
<hasaudio>yes</hasaudio>
<name>Mailbox</name>
<devicename>Dahua Technology</devicename>
<video-format>H.265</video-format>
<audio-format>AAC</audio-format>
</camera>`

var cam securityspy.Camera
require.NoError(t, xml.Unmarshal([]byte(camXML), &cam))
require.Equal(t, "Mailbox", cam.Name)
require.Equal(t, 3072, cam.Width)
require.Equal(t, 1728, cam.Height)
require.Equal(t, "H.265", cam.VideoFormat)
require.Equal(t, "AAC", cam.AudioFormat)
require.True(t, cam.ModeM.Val)
require.True(t, cam.HasAudio.Val)
require.Equal(t, "Dahua Technology", cam.DeviceName)
require.Equal(t, "h265", cam.PreferredVCodec())
}

func TestAll(t *testing.T) {
t.Parallel()
asert := assert.New(t)
Expand Down
37 changes: 29 additions & 8 deletions cameras_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -337,17 +337,13 @@ type cameraXML struct {

// UnmarshalXML decodes v5 or v6 ++systemInfo camera elements into Camera.
//
//nolint:cyclop,funlen // dual schema mapping
//nolint:funlen // dual schema mapping
func (c *Camera) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
var raw cameraXML
if err := d.DecodeElement(&raw, &start); err != nil {
return fmt.Errorf("decoding camera xml: %w", err)
}

isV6 := raw.WidthV6 != 0 || raw.HeightV6 != 0 || raw.CapturePathV6 != "" ||
raw.DeviceNameV6 != "" || raw.ModeCV6.Txt != "" || raw.HasAudioV6.Txt != "" ||
raw.PTZV6 != nil || raw.VideoFormat != "" || raw.StoragePathSet()

c.Number = raw.Number
c.Connected = raw.Connected
c.Name = raw.Name
Expand All @@ -363,7 +359,7 @@ func (c *Camera) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
c.Overlay = raw.Overlay
c.OverlayText = firstNonEmpty(raw.OverlayTextV6, raw.OverlayText)

if isV6 {
if raw.isV6() {
c.Width = raw.WidthV6
c.Height = raw.HeightV6
c.ModeC = raw.ModeCV6
Expand Down Expand Up @@ -440,6 +436,15 @@ func (c *Camera) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
c.TLrecordAudio = raw.TLrecordAudio
}

// Prefer the other schema's dimensions when the chosen branch left them unset.
if c.Width == 0 {
c.Width = firstNonZero(raw.WidthV5, raw.WidthV6)
}

if c.Height == 0 {
c.Height = firstNonZero(raw.HeightV5, raw.HeightV6)
}

c.NetworkAudio = c.AudioNetwork
c.ActionSoundCam = raw.ActionSoundCam
c.ActionSoundMac = raw.ActionSoundMac
Expand Down Expand Up @@ -474,8 +479,14 @@ func (c *Camera) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
return nil
}

func (x *cameraXML) StoragePathSet() bool {
return x.CapturePathV6 != ""
// isV6 reports whether this camera element used SecuritySpy 6+ tags
// (video-width/height, storage-path, device-name, cc-mode, has-audio, ptz-features).
// Shared fields like video-format also appear on SS 5.5's v5 schema, so they
// must not be used as the sole signal.
func (x *cameraXML) isV6() bool {
return x.WidthV6 != 0 || x.HeightV6 != 0 || x.CapturePathV6 != "" ||
x.DeviceNameV6 != "" || x.ModeCV6.Txt != "" || x.HasAudioV6.Txt != "" ||
x.PTZV6 != nil
}

func firstNonEmpty(values ...string) string {
Expand All @@ -487,3 +498,13 @@ func firstNonEmpty(values ...string) string {

return ""
}

func firstNonZero(values ...int) int {
for _, v := range values {
if v != 0 {
return v
}
}

return 0
}
Loading