diff --git a/TODO.md b/TODO.md
index 66ba03b..d9505f9 100644
--- a/TODO.md
+++ b/TODO.md
@@ -30,13 +30,13 @@
- [ ] getServices
- [ ] getServiceCapabilities
- [ ] OnvifServiceMedia
- - [ ] getStreamUri
+ - [X] getProfiles
+ - [X] getStreamUri
- [ ] getVideoEncoderConfigurations
- [ ] getVideoEncoderConfiguration
- [ ] getCompatibleVideoEncoderConfigurations
- [ ] getVideoEncoderConfigurationOptions
- [ ] getGuaranteedNumberOfVideoEncoderInstances
- - [ ] getProfiles
- [ ] getProfile
- [ ] createProfile
- [ ] deleteProfile
diff --git a/media.go b/media.go
new file mode 100644
index 0000000..975b581
--- /dev/null
+++ b/media.go
@@ -0,0 +1,159 @@
+package onvif
+
+var mediaXMLNs = []string{
+ `xmlns:trt="http://www.onvif.org/ver10/media/wsdl"`,
+ `xmlns:tt="http://www.onvif.org/ver10/schema"`,
+}
+
+// GetProfiles fetch available media profiles of ONVIF camera
+func (device Device) GetProfiles() ([]MediaProfile, error) {
+ // Create SOAP
+ soap := SOAP{
+ Body: "",
+ XMLNs: mediaXMLNs,
+ }
+
+ // Send SOAP request
+ response, err := soap.SendRequest(device.XAddr)
+ if err != nil {
+ return []MediaProfile{}, err
+ }
+
+ // Get and parse list of profile to interface
+ ifaceProfiles, err := response.ValuesForPath("Envelope.Body.GetProfilesResponse.Profiles")
+ if err != nil {
+ return []MediaProfile{}, err
+ }
+
+ // Create initial result
+ result := []MediaProfile{}
+
+ // Parse each available profile
+ for _, ifaceProfile := range ifaceProfiles {
+ if mapProfile, ok := ifaceProfile.(map[string]interface{}); ok {
+ // Parse name and token
+ profile := MediaProfile{}
+ profile.Name = interfaceToString(mapProfile["Name"])
+ profile.Token = interfaceToString(mapProfile["-token"])
+
+ // Parse video source configuration
+ videoSource := MediaSourceConfig{}
+ if mapVideoSource, ok := mapProfile["VideoSourceConfiguration"].(map[string]interface{}); ok {
+ videoSource.Name = interfaceToString(mapVideoSource["Name"])
+ videoSource.Token = interfaceToString(mapVideoSource["-token"])
+ videoSource.SourceToken = interfaceToString(mapVideoSource["SourceToken"])
+
+ // Parse video bounds
+ bounds := MediaBounds{}
+ if mapVideoBounds, ok := mapVideoSource["Bounds"].(map[string]interface{}); ok {
+ bounds.Height = interfaceToInt(mapVideoBounds["-height"])
+ bounds.Width = interfaceToInt(mapVideoBounds["-width"])
+ }
+ videoSource.Bounds = bounds
+ }
+ profile.VideoSourceConfig = videoSource
+
+ // Parse video encoder configuration
+ videoEncoder := VideoEncoderConfig{}
+ if mapVideoEncoder, ok := mapProfile["VideoEncoderConfiguration"].(map[string]interface{}); ok {
+ videoEncoder.Name = interfaceToString(mapVideoEncoder["Name"])
+ videoEncoder.Token = interfaceToString(mapVideoEncoder["-token"])
+ videoEncoder.Encoding = interfaceToString(mapVideoEncoder["Encoding"])
+ videoEncoder.Quality = interfaceToInt(mapVideoEncoder["Quality"])
+ videoEncoder.SessionTimeout = interfaceToString(mapVideoEncoder["SessionTimeout"])
+
+ // Parse video rate control
+ rateControl := VideoRateControl{}
+ if mapVideoRate, ok := mapVideoEncoder["RateControl"].(map[string]interface{}); ok {
+ rateControl.BitrateLimit = interfaceToInt(mapVideoRate["BitrateLimit"])
+ rateControl.EncodingInterval = interfaceToInt(mapVideoRate["EncodingInterval"])
+ rateControl.FrameRateLimit = interfaceToInt(mapVideoRate["FrameRateLimit"])
+ }
+ videoEncoder.RateControl = rateControl
+
+ // Parse video resolution
+ resolution := MediaBounds{}
+ if mapVideoRes, ok := mapVideoEncoder["Resolution"].(map[string]interface{}); ok {
+ resolution.Height = interfaceToInt(mapVideoRes["Height"])
+ resolution.Width = interfaceToInt(mapVideoRes["Width"])
+ }
+ videoEncoder.Resolution = resolution
+ }
+ profile.VideoEncoderConfig = videoEncoder
+
+ // Parse audio source configuration
+ audioSource := MediaSourceConfig{}
+ if mapAudioSource, ok := mapProfile["AudioSourceConfiguration"].(map[string]interface{}); ok {
+ audioSource.Name = interfaceToString(mapAudioSource["Name"])
+ audioSource.Token = interfaceToString(mapAudioSource["-token"])
+ audioSource.SourceToken = interfaceToString(mapAudioSource["SourceToken"])
+ }
+ profile.AudioSourceConfig = audioSource
+
+ // Parse audio encoder configuration
+ audioEncoder := AudioEncoderConfig{}
+ if mapAudioEncoder, ok := mapProfile["AudioEncoderConfiguration"].(map[string]interface{}); ok {
+ audioEncoder.Name = interfaceToString(mapAudioEncoder["Name"])
+ audioEncoder.Token = interfaceToString(mapAudioEncoder["-token"])
+ audioEncoder.Encoding = interfaceToString(mapAudioEncoder["Encoding"])
+ audioEncoder.Bitrate = interfaceToInt(mapAudioEncoder["Bitrate"])
+ audioEncoder.SampleRate = interfaceToInt(mapAudioEncoder["SampleRate"])
+ audioEncoder.SessionTimeout = interfaceToString(mapAudioEncoder["SessionTimeout"])
+ }
+ profile.AudioEncoderConfig = audioEncoder
+
+ // Parse PTZ configuration
+ ptzConfig := PTZConfig{}
+ if mapPTZ, ok := mapProfile["PTZConfiguration"].(map[string]interface{}); ok {
+ ptzConfig.Name = interfaceToString(mapPTZ["Name"])
+ ptzConfig.Token = interfaceToString(mapPTZ["-token"])
+ ptzConfig.NodeToken = interfaceToString(mapPTZ["NodeToken"])
+ }
+ profile.PTZConfig = ptzConfig
+
+ // Push profile to result
+ result = append(result, profile)
+ }
+ }
+
+ return result, nil
+}
+
+// GetStreamURI fetch stream URI of a media profile.
+// Possible protocol is UDP, HTTP or RTSP
+func (device Device) GetStreamURI(profileToken, protocol string) (MediaURI, error) {
+ // Create SOAP
+ soap := SOAP{
+ XMLNs: mediaXMLNs,
+ Body: `
+
+ RTP-Unicast
+ ` + protocol + `
+
+ ` + profileToken + `
+ `,
+ }
+
+ // Send SOAP request
+ response, err := soap.SendRequest(device.XAddr)
+ if err != nil {
+ return MediaURI{}, err
+ }
+
+ // Parse response to interface
+ ifaceURI, err := response.ValueForPath("Envelope.Body.GetStreamUriResponse.MediaUri")
+ if err != nil {
+ return MediaURI{}, err
+ }
+
+ // Parse interface to struct
+ streamURI := MediaURI{}
+ if mapURI, ok := ifaceURI.(map[string]interface{}); ok {
+ streamURI.URI = interfaceToString(mapURI["Uri"])
+ streamURI.Timeout = interfaceToString(mapURI["Timeout"])
+ streamURI.InvalidAfterConnect = interfaceToBool(mapURI["InvalidAfterConnect"])
+ streamURI.InvalidAfterReboot = interfaceToBool(mapURI["InvalidAfterReboot"])
+ }
+
+ return streamURI, nil
+}
diff --git a/media_test.go b/media_test.go
new file mode 100644
index 0000000..5fa655f
--- /dev/null
+++ b/media_test.go
@@ -0,0 +1,31 @@
+package onvif
+
+import (
+ "fmt"
+ "log"
+ "testing"
+)
+
+func TestGetProfiles(t *testing.T) {
+ log.Println("Test GetProfiles")
+
+ res, err := testDevice.GetProfiles()
+ if err != nil {
+ t.Error(err)
+ }
+
+ js := prettyJSON(&res)
+ fmt.Println(js)
+}
+
+func TestGetStreamURI(t *testing.T) {
+ log.Println("Test GetStreamURI")
+
+ res, err := testDevice.GetStreamURI("IPCProfilesToken0", "UDP")
+ if err != nil {
+ t.Error(err)
+ }
+
+ js := prettyJSON(&res)
+ fmt.Println(js)
+}
diff --git a/model.go b/model.go
index bd704c5..47c0129 100644
--- a/model.go
+++ b/model.go
@@ -38,3 +38,71 @@ type HostnameInformation struct {
Name string
FromDHCP bool
}
+
+// MediaBounds contains resolution of a video media
+type MediaBounds struct {
+ Height int
+ Width int
+}
+
+// MediaSourceConfig contains configuration of a media source
+type MediaSourceConfig struct {
+ Name string
+ Token string
+ SourceToken string
+ Bounds MediaBounds
+}
+
+// VideoRateControl contains rate control of a video
+type VideoRateControl struct {
+ BitrateLimit int
+ EncodingInterval int
+ FrameRateLimit int
+}
+
+// VideoEncoderConfig contains configuration of a video encoder
+type VideoEncoderConfig struct {
+ Name string
+ Token string
+ Encoding string
+ Quality int
+ RateControl VideoRateControl
+ Resolution MediaBounds
+ SessionTimeout string
+}
+
+// AudioEncoderConfig contains configuration of an audio encoder
+type AudioEncoderConfig struct {
+ Name string
+ Token string
+ Encoding string
+ Bitrate int
+ SampleRate int
+ SessionTimeout string
+}
+
+// PTZConfig contains configuration of a PTZ control in camera
+type PTZConfig struct {
+ Name string
+ Token string
+ NodeToken string
+}
+
+// MediaProfile contains media profile of an ONVIF camera
+type MediaProfile struct {
+ Name string
+ Token string
+ VideoSourceConfig MediaSourceConfig
+ VideoEncoderConfig VideoEncoderConfig
+ AudioSourceConfig MediaSourceConfig
+ AudioEncoderConfig AudioEncoderConfig
+ PTZConfig PTZConfig
+}
+
+// MediaURI contains streaming URI of an ONVIF camera
+type MediaURI struct {
+ URI string
+ Timeout string
+ InvalidAfterConnect bool
+ InvalidAfterReboot bool
+}
diff --git a/utils.go b/utils.go
index e6677ea..895c7be 100644
--- a/utils.go
+++ b/utils.go
@@ -2,6 +2,7 @@ package onvif
import (
"encoding/json"
+ "strconv"
"strings"
)
@@ -19,6 +20,12 @@ func interfaceToBool(src interface{}) bool {
return strings.ToLower(strBool) == "true"
}
+func interfaceToInt(src interface{}) int {
+ strNumber := interfaceToString(src)
+ number, _ := strconv.Atoi(strNumber)
+ return number
+}
+
func prettyJSON(src interface{}) string {
result, _ := json.MarshalIndent(&src, "", " ")
return string(result)