Improve support ONVIF client

This commit is contained in:
Alexey Khit
2023-05-03 08:02:56 +03:00
parent 6d9d89bbe3
commit 4d53889519
4 changed files with 77 additions and 49 deletions
+22 -6
View File
@@ -11,6 +11,7 @@ import (
"io"
"net"
"net/http"
"net/url"
"os"
"strconv"
"time"
@@ -123,17 +124,32 @@ func apiOnvif(w http.ResponseWriter, r *http.Request) {
var items []api.Stream
if src == "" {
hosts, err := onvif.DiscoveryStreamingHosts()
urls, err := onvif.DiscoveryStreamingURLs()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
for _, host := range hosts {
items = append(items, api.Stream{
Name: host,
URL: "onvif://user:pass@" + host,
})
for _, rawURL := range urls {
u, err := url.Parse(rawURL)
if err != nil {
log.Warn().Str("url", rawURL).Msg("[onvif] broken")
continue
}
if u.Scheme != "http" {
log.Warn().Str("url", rawURL).Msg("[onvif] unsupported")
continue
}
u.Scheme = "onvif"
u.User = url.UserPassword("user", "pass")
if u.Path == onvif.PathDevice {
u.Path = ""
}
items = append(items, api.Stream{Name: u.Host, URL: u.String()})
}
} else {
client, err := onvif.NewClient(src)
+43 -21
View File
@@ -17,6 +17,10 @@ import (
type Client struct {
url *url.URL
deviceURL string
mediaURL string
imaginURL string
}
func NewClient(rawURL string) (*Client, error) {
@@ -24,7 +28,25 @@ func NewClient(rawURL string) (*Client, error) {
if err != nil {
return nil, err
}
return &Client{url: u}, nil
baseURL := "http://" + u.Host
client := &Client{url: u}
if u.Path == "" {
client.deviceURL = baseURL + PathDevice
} else {
client.deviceURL = baseURL + u.Path
}
b, err := client.GetCapabilities()
if err != nil {
return nil, err
}
client.mediaURL = FindTagValue(b, "Media.+?XAddr")
client.imaginURL = FindTagValue(b, "Imaging.+?XAddr")
return client, nil
}
func (c *Client) GetURI() (string, error) {
@@ -39,7 +61,7 @@ func (c *Client) GetURI() (string, error) {
return "", err
}
if i >= len(tokens) {
return "", errors.New("wrong subtype")
return "", errors.New("onvif: wrong subtype")
}
token = tokens[i]
}
@@ -104,7 +126,7 @@ func (c *Client) HasSnapshots() bool {
func (c *Client) GetCapabilities() ([]byte, error) {
return c.Request(
PathDevice,
c.deviceURL,
`<tds:GetCapabilities xmlns:tds="http://www.onvif.org/ver10/device/wsdl">
<tds:Category>All</tds:Category>
</tds:GetCapabilities>`,
@@ -113,25 +135,25 @@ func (c *Client) GetCapabilities() ([]byte, error) {
func (c *Client) GetNetworkInterfaces() ([]byte, error) {
return c.Request(
PathDevice, `<tds:GetNetworkInterfaces xmlns:tds="http://www.onvif.org/ver10/device/wsdl"/>`,
c.deviceURL, `<tds:GetNetworkInterfaces xmlns:tds="http://www.onvif.org/ver10/device/wsdl"/>`,
)
}
func (c *Client) GetDeviceInformation() ([]byte, error) {
return c.Request(
PathDevice, `<tds:GetDeviceInformation xmlns:tds="http://www.onvif.org/ver10/device/wsdl"/>`,
c.deviceURL, `<tds:GetDeviceInformation xmlns:tds="http://www.onvif.org/ver10/device/wsdl"/>`,
)
}
func (c *Client) GetProfiles() ([]byte, error) {
return c.Request(
PathMedia, `<trt:GetProfiles xmlns:trt="http://www.onvif.org/ver10/media/wsdl"/>`,
c.mediaURL, `<trt:GetProfiles xmlns:trt="http://www.onvif.org/ver10/media/wsdl"/>`,
)
}
func (c *Client) GetStreamUri(token string) ([]byte, error) {
return c.Request(
PathMedia,
c.mediaURL,
`<trt:GetStreamUri xmlns:trt="http://www.onvif.org/ver10/media/wsdl" xmlns:tt="http://www.onvif.org/ver10/schema">
<trt:StreamSetup>
<tt:Stream>RTP-Unicast</tt:Stream>
@@ -144,8 +166,8 @@ func (c *Client) GetStreamUri(token string) ([]byte, error) {
func (c *Client) GetSnapshotUri(token string) ([]byte, error) {
return c.Request(
PathMedia,
`<trt:GetSnapshotUri xmlns:trt="http://www.onvif.org/ver10/media/wsdl">
c.imaginURL,
`<trt:GetSnapshotUri xmlns:trt="http://www.onvif.org/ver10/media/wsdl">
<trt:ProfileToken>`+token+`</trt:ProfileToken>
</trt:GetSnapshotUri>`,
)
@@ -153,26 +175,26 @@ func (c *Client) GetSnapshotUri(token string) ([]byte, error) {
func (c *Client) GetSystemDateAndTime() ([]byte, error) {
return c.Request(
PathDevice, `<tds:GetSystemDateAndTime xmlns:tds="http://www.onvif.org/ver10/device/wsdl"/>`,
c.deviceURL, `<tds:GetSystemDateAndTime xmlns:tds="http://www.onvif.org/ver10/device/wsdl"/>`,
)
}
func (c *Client) GetServiceCapabilities() ([]byte, error) {
// some cameras answer GetServiceCapabilities for media only for path = "/onvif/media"
return c.Request(
PathMedia, `<trt:GetServiceCapabilities xmlns:trt="http://www.onvif.org/ver10/media/wsdl"/>`,
c.mediaURL, `<trt:GetServiceCapabilities xmlns:trt="http://www.onvif.org/ver10/media/wsdl"/>`,
)
}
func (c *Client) SystemReboot() ([]byte, error) {
return c.Request(
PathDevice, `<tds:SystemReboot xmlns:tds="http://www.onvif.org/ver10/device/wsdl"/>`,
c.deviceURL, `<tds:SystemReboot xmlns:tds="http://www.onvif.org/ver10/device/wsdl"/>`,
)
}
func (c *Client) GetServices() ([]byte, error) {
return c.Request(
PathDevice, `<tds:GetServices xmlns:tds="http://www.onvif.org/ver10/device/wsdl">
c.deviceURL, `<tds:GetServices xmlns:tds="http://www.onvif.org/ver10/device/wsdl">
<tds:IncludeCapability>true</tds:IncludeCapability>
</tds:GetServices>`,
)
@@ -180,11 +202,15 @@ func (c *Client) GetServices() ([]byte, error) {
func (c *Client) GetScopes() ([]byte, error) {
return c.Request(
PathDevice, `<tds:GetScopes xmlns:tds="http://www.onvif.org/ver10/device/wsdl" />`,
c.deviceURL, `<tds:GetScopes xmlns:tds="http://www.onvif.org/ver10/device/wsdl" />`,
)
}
func (c *Client) Request(path, body string) ([]byte, error) {
func (c *Client) Request(url, body string) ([]byte, error) {
if url == "" {
return nil, errors.New("onvif: unsupported service")
}
buf := bytes.NewBuffer(nil)
buf.WriteString(
`<?xml version="1.0" encoding="UTF-8"?><s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">`,
@@ -213,11 +239,7 @@ func (c *Client) Request(path, body string) ([]byte, error) {
buf.WriteString(`<s:Body>` + body + `</s:Body></s:Envelope>`)
client := &http.Client{Timeout: time.Second * 5000}
res, err := client.Post(
"http://"+c.url.Host+path,
`application/soap+xml;charset=utf-8`,
buf,
)
res, err := client.Post(url, `application/soap+xml;charset=utf-8`, buf)
if err != nil {
return nil, err
}
@@ -226,7 +248,7 @@ func (c *Client) Request(path, body string) ([]byte, error) {
b, err := io.ReadAll(res.Body)
if err == nil && res.StatusCode != http.StatusOK {
err = errors.New(res.Status)
err = errors.New("onvif: " + res.Status + " for " + url)
}
return b, err
+11 -22
View File
@@ -3,7 +3,6 @@ package onvif
import (
"github.com/AlexxIT/go2rtc/pkg/core"
"net"
"net/url"
"regexp"
"strconv"
"strings"
@@ -12,11 +11,10 @@ import (
const (
PathDevice = "/onvif/device_service"
PathMedia = "/onvif/media_service"
)
func FindTagValue(b []byte, tag string) string {
re := regexp.MustCompile(tag + `[^>]*>([^<]+)`)
re := regexp.MustCompile(`<[^/>]*` + tag + `[^>]*>([^<]+)`)
m := re.FindSubmatch(b)
if len(m) != 2 {
return ""
@@ -30,7 +28,7 @@ func UUID() string {
return s[:8] + "-" + s[8:12] + "-" + s[12:16] + "-" + s[16:20] + "-" + s[20:]
}
func DiscoveryStreamingHosts() ([]string, error) {
func DiscoveryStreamingURLs() ([]string, error) {
conn, err := net.ListenUDP("udp4", nil)
if err != nil {
return nil, err
@@ -66,7 +64,7 @@ func DiscoveryStreamingHosts() ([]string, error) {
return nil, err
}
var hosts []string
var urls []string
b := make([]byte, 8192)
for {
@@ -75,37 +73,28 @@ func DiscoveryStreamingHosts() ([]string, error) {
break
}
//log.Printf("[onvif] discovery response addr=%s:\n%s", addr, b[:n])
// ignore printers, etc
if !strings.Contains(string(b[:n]), "onvif") {
continue
}
//log.Printf("[onvif] discovery response:\n%s", b[:n])
rawURL := FindTagValue(b[:n], "XAddrs")
if rawURL == "" {
continue
}
u, err := url.Parse(rawURL)
if err != nil {
continue
}
if u.Scheme != "http" {
url := FindTagValue(b[:n], "XAddrs")
if url == "" {
continue
}
// fix some buggy cameras
// <wsdd:XAddrs>http://0.0.0.0:8080/onvif/device_service</wsdd:XAddrs>
if strings.HasPrefix(u.Host, "0.0.0.0") {
u.Host = addr.IP.String() + u.Host[7:]
if strings.HasPrefix(url, "http://0.0.0.0") {
url = "http://" + addr.IP.String() + url[14:]
}
hosts = append(hosts, u.Host)
urls = append(urls, url)
}
return hosts, nil
return urls, nil
}
func atoi(s string) int {
+1
View File
@@ -60,6 +60,7 @@
<script>
async function getStreams(url, tableID) {
const table = document.getElementById(tableID)
table.innerText = 'loading...'
const r = typeof url === 'string' ? await fetch(url, {cache: 'no-cache'}) : url
if (!r.ok) {