diff --git a/cmd/onvif/init.go b/cmd/onvif/init.go index 928b77d4c..ae8101fa6 100644 --- a/cmd/onvif/init.go +++ b/cmd/onvif/init.go @@ -1,6 +1,13 @@ package onvif import ( + "io" + "net" + "net/http" + "os" + "strconv" + "time" + "github.com/AlexxIT/go2rtc/cmd/api" "github.com/AlexxIT/go2rtc/cmd/app" "github.com/AlexxIT/go2rtc/cmd/rtsp" @@ -8,12 +15,6 @@ import ( "github.com/AlexxIT/go2rtc/pkg/core" "github.com/AlexxIT/go2rtc/pkg/onvif" "github.com/rs/zerolog" - "io" - "net" - "net/http" - "os" - "strconv" - "time" ) func Init() { @@ -132,7 +133,7 @@ func apiOnvif(w http.ResponseWriter, r *http.Request) { for _, host := range hosts { items = append(items, api.Stream{ Name: host, - URL: "onvif://user:pass@" + host, + URL: "onvif://" + host, }) } } else { @@ -155,18 +156,25 @@ func apiOnvif(w http.ResponseWriter, r *http.Request) { } for i, token := range tokens { + + streamuri, err := client.GetStreamUri(token) + if err != nil || streamuri == "" { + continue + } items = append(items, api.Stream{ Name: name + " stream" + strconv.Itoa(i), URL: src + "?subtype=" + token, }) - } - - if len(tokens) > 0 && client.HasSnapshots() { + snapshoturi, err := client.GetSnapshotUri(token) + if err != nil || snapshoturi == "" { + continue + } items = append(items, api.Stream{ - Name: name + " snapshot", + Name: name + " snapshot" + strconv.Itoa(i), URL: src + "?subtype=" + tokens[0] + "&snapshot", }) } + } api.ResponseStreams(w, items) diff --git a/go.mod b/go.mod index beae7f799..6f31fbb01 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/AlexxIT/go2rtc go 1.20 require ( + github.com/antchfx/xmlquery v1.3.15 github.com/brutella/hap v0.0.17 github.com/deepch/vdk v0.0.19 github.com/gorilla/websocket v1.5.0 @@ -24,9 +25,11 @@ require ( ) require ( + github.com/antchfx/xpath v1.2.4 // indirect github.com/brutella/dnssd v1.2.5 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-chi/chi v1.5.4 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/google/uuid v1.3.0 // indirect github.com/kr/pretty v0.2.1 // indirect github.com/mattn/go-colorable v0.1.13 // indirect @@ -45,9 +48,9 @@ require ( github.com/xiam/to v0.0.0-20200126224905-d60d31e03561 // indirect golang.org/x/crypto v0.7.0 // indirect golang.org/x/mod v0.9.0 // indirect - golang.org/x/net v0.8.0 // indirect - golang.org/x/sys v0.6.0 // indirect - golang.org/x/text v0.8.0 // indirect + golang.org/x/net v0.9.0 // indirect + golang.org/x/sys v0.7.0 // indirect + golang.org/x/text v0.9.0 // indirect golang.org/x/tools v0.7.0 // indirect ) diff --git a/go.sum b/go.sum index 20a2e4ee3..c4d2a0bab 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,11 @@ github.com/AlexxIT/hap v0.0.15-0.20221108133010-d8a45b7a7045 h1:xJf3FxQJReJSDyYX github.com/AlexxIT/hap v0.0.15-0.20221108133010-d8a45b7a7045/go.mod h1:QNA3sm16zE5uUyC8+E/gNkMvQWjqQLuxQKkU5PMi8N4= github.com/AlexxIT/vdk v0.0.18-0.20221108193131-6168555b4f92 h1:cIeYMGaAirSZnrKRDTb5VgZDDYqPLhYiczElMg4sQW0= github.com/AlexxIT/vdk v0.0.18-0.20221108193131-6168555b4f92/go.mod h1:7ydHfSkflMZxBXfWR79dMjrT54xzvLxnPaByOa9Jpzg= +github.com/antchfx/xmlquery v1.3.15 h1:aJConNMi1sMha5G8YJoAIF5P+H+qG1L73bSItWHo8Tw= +github.com/antchfx/xmlquery v1.3.15/go.mod h1:zMDv5tIGjOxY/JCNNinnle7V/EwthZ5IT8eeCGJKRWA= +github.com/antchfx/xpath v1.2.3/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs= +github.com/antchfx/xpath v1.2.4 h1:dW1HB/JxKvGtJ9WyVGJ0sIoEcqftV3SqIstujI+B9XY= +github.com/antchfx/xpath v1.2.4/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs= github.com/brutella/dnssd v1.2.3/go.mod h1:JoW2sJUrmVIef25G6lrLj7HS6Xdwh6q8WUIvMkkBYXs= github.com/brutella/dnssd v1.2.5 h1:b8syhho41/5ikw3X2X4baR9NWEBSlpZnfQgujsv7bk4= github.com/brutella/dnssd v1.2.5/go.mod h1:JoW2sJUrmVIef25G6lrLj7HS6Xdwh6q8WUIvMkkBYXs= @@ -15,6 +20,8 @@ github.com/go-chi/chi v1.5.4 h1:QHdzF2szwjqVV4wmByUnTcsbIg7UGaQ0tPF2t5GcAIs= github.com/go-chi/chi v1.5.4/go.mod h1:uaf8YgoFazUOkPBG7fxPftUylNumIev9awIWOENIuEg= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= @@ -157,8 +164,8 @@ golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -189,8 +196,8 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -203,8 +210,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= diff --git a/pkg/onvif/client.go b/pkg/onvif/client.go index 77157d0b1..905428333 100644 --- a/pkg/onvif/client.go +++ b/pkg/onvif/client.go @@ -5,18 +5,22 @@ import ( "crypto/sha1" "encoding/base64" "errors" + "fmt" "github.com/AlexxIT/go2rtc/pkg/core" - "html" + "github.com/antchfx/xmlquery" + "io" "net/http" "net/url" "regexp" - "strings" "time" ) type Client struct { - url *url.URL + url *url.URL + media_service string + image_service string + device_service string } func NewClient(rawURL string) (*Client, error) { @@ -24,7 +28,8 @@ func NewClient(rawURL string) (*Client, error) { if err != nil { return nil, err } - return &Client{url: u}, nil + client := Client{url: u, device_service: "onvif/device_service"} + return &client, nil } func (c *Client) GetURI() (string, error) { @@ -49,14 +54,11 @@ func (c *Client) GetURI() (string, error) { getUri = c.GetSnapshotUri } - b, err := getUri(token) + uri, err := getUri(token) if err != nil { return "", err } - uri := FindTagValue(b, "Uri") - uri = html.UnescapeString(uri) - u, err := url.Parse(uri) if err != nil { return "", err @@ -94,81 +96,113 @@ func (c *Client) GetProfilesTokens() ([]string, error) { return tokens, nil } -func (c *Client) HasSnapshots() bool { - b, err := c.GetServiceCapabilities() +func (c *Client) GetCapabilities() ([]byte, error) { + response, err := c.Request( + ` + All +`, c.device_service, + ) + doc, err := xmlquery.Parse(bytes.NewReader(response)) if err != nil { - return false + return nil, err } - return strings.Contains(string(b), `SnapshotUri="true"`) -} -func (c *Client) GetCapabilities() ([]byte, error) { - return c.Request( - ` - All -`, - ) + c.media_service = xmlquery.FindOne(doc, "//tt:Media/tt:XAddr").InnerText() + c.image_service = xmlquery.FindOne(doc, "//tt:Imaging/tt:XAddr").InnerText() + return response, err } func (c *Client) GetNetworkInterfaces() ([]byte, error) { - return c.Request(``) + return c.Request(``, c.device_service) } func (c *Client) GetDeviceInformation() ([]byte, error) { - return c.Request(``) + return c.Request(``, c.device_service) } func (c *Client) GetProfiles() ([]byte, error) { - return c.Request(``) + return c.Request(``, c.media_service) } -func (c *Client) GetStreamUri(token string) ([]byte, error) { - return c.Request( +func (c *Client) GetStreamUri(token string) (string, error) { + fmt.Println("Query Stream URI with token " + token) + response, err := c.Request( ` RTP-Unicast RTSP - ` + token + ` -`) + `+token+` +`, c.media_service) + if err != nil { + return "", err + } + doc, err := xmlquery.Parse(bytes.NewReader(response)) + if err != nil { + return "", err + } + + stream_url, err := xmlquery.Query(doc, "//trt:MediaUri/tt:Uri") + if err != nil { + fmt.Printf("Error: %v", err) + return "", err + } + fmt.Println("Stream URI: " + stream_url.InnerText()) + return stream_url.InnerText(), err } -func (c *Client) GetSnapshotUri(token string) ([]byte, error) { - return c.Request( - ` - ` + token + ` -`) +func (c *Client) GetSnapshotUri(token string) (string, error) { + fmt.Println("Query Snaphot URI with token " + token) + response, err := c.Request( + ` + `+token+` +`, c.media_service) + if err != nil { + return "", err + } + doc, err := xmlquery.Parse(bytes.NewReader(response)) + if err != nil { + return "", err + } + + stream_url, err := xmlquery.Query(doc, "//trt:MediaUri/tt:Uri") + if err != nil { + fmt.Printf("Error: %v", err) + return "", err + } + fmt.Println("Snaphot URI: " + stream_url.InnerText()) + return stream_url.InnerText(), err } func (c *Client) GetSystemDateAndTime() ([]byte, error) { return c.Request( - ``, + ``, c.device_service, ) } func (c *Client) GetServiceCapabilities() ([]byte, error) { return c.Request( - ``, + ``, c.device_service, ) } func (c *Client) SystemReboot() ([]byte, error) { return c.Request( - ``, + ``, c.device_service, ) } func (c *Client) GetServices() ([]byte, error) { return c.Request(` true -`) +`, c.device_service) } func (c *Client) GetScopes() ([]byte, error) { - return c.Request(``) + return c.Request(``, c.device_service) } -func (c *Client) Request(body string) ([]byte, error) { +func (c *Client) Request(body string, path string) ([]byte, error) { buf := bytes.NewBuffer(nil) buf.WriteString( ``, @@ -198,7 +232,7 @@ func (c *Client) Request(body string) ([]byte, error) { client := &http.Client{Timeout: time.Second * 5000} res, err := client.Post( - "http://"+c.url.Host+"/onvif/", + "http://"+c.url.Host+"/"+path, `application/soap+xml;charset=utf-8`, buf, ) diff --git a/pkg/onvif/helpers.go b/pkg/onvif/helpers.go index dd12e6bc3..3d45ea88b 100644 --- a/pkg/onvif/helpers.go +++ b/pkg/onvif/helpers.go @@ -30,20 +30,22 @@ func DiscoveryStreamingHosts() ([]string, error) { return nil, err } - msg := ` - - - http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe - uuid:` + UUID() + ` - urn:schemas-xmlsoap-org:ws:2005:04:discovery - - - - tds:Device - onvif://www.onvif.org/Profile/Streaming - - -` + msg := ` +
+ urn:uuid:` + UUID() + ` + urn:schemas-xmlsoap-org:ws:2005:04:discovery + http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe +
+ + + + + + +
` addr := &net.UDPAddr{ IP: net.IP{239, 255, 255, 250}, diff --git a/www/add.html b/www/add.html index 476c65c56..2bcfbb755 100644 --- a/www/add.html +++ b/www/add.html @@ -204,6 +204,15 @@ await getStreams(url.toString(), 'onvif-table') }) + + // Add event listener to the table src cell + document.getElementById('onvif-table').addEventListener('click', ev => { + const target = ev.target; + if (target.tagName === 'TD' && target.cellIndex === 1) { // Check if the clicked cell is in the "Source" column + const inputBox = document.querySelector('#onvif-form input[name="src"]'); + inputBox.value = target.textContent; + } + });