mirror of
https://github.com/AlexxIT/go2rtc.git
synced 2025-09-27 04:36:12 +08:00
227 lines
5.6 KiB
Go
227 lines
5.6 KiB
Go
package onvif
|
|
|
|
import (
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/AlexxIT/go2rtc/internal/api"
|
|
"github.com/AlexxIT/go2rtc/internal/app"
|
|
"github.com/AlexxIT/go2rtc/internal/rtsp"
|
|
"github.com/AlexxIT/go2rtc/internal/streams"
|
|
"github.com/AlexxIT/go2rtc/pkg/core"
|
|
"github.com/AlexxIT/go2rtc/pkg/onvif"
|
|
"github.com/rs/zerolog"
|
|
)
|
|
|
|
func Init() {
|
|
log = app.GetLogger("onvif")
|
|
|
|
streams.HandleFunc("onvif", streamOnvif)
|
|
|
|
// ONVIF server on all suburls
|
|
api.HandleFunc("/onvif/", onvifDeviceService)
|
|
|
|
// ONVIF client autodiscovery
|
|
api.HandleFunc("api/onvif", apiOnvif)
|
|
}
|
|
|
|
var log zerolog.Logger
|
|
|
|
func streamOnvif(rawURL string) (core.Producer, error) {
|
|
client, err := onvif.NewClient(rawURL)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
uri, err := client.GetURI()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
log.Debug().Msgf("[onvif] new uri=%s", uri)
|
|
|
|
return streams.GetProducer(uri)
|
|
}
|
|
|
|
func onvifDeviceService(w http.ResponseWriter, r *http.Request) {
|
|
b, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
operation := onvif.GetRequestAction(b)
|
|
if operation == "" {
|
|
http.Error(w, "malformed request body", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
log.Trace().Msgf("[onvif] server request %s %s:\n%s", r.Method, r.RequestURI, b)
|
|
|
|
switch operation {
|
|
case onvif.DeviceGetNetworkInterfaces, // important for Hass
|
|
onvif.DeviceGetSystemDateAndTime, // important for Hass
|
|
onvif.DeviceGetDiscoveryMode,
|
|
onvif.DeviceGetDNS,
|
|
onvif.DeviceGetHostname,
|
|
onvif.DeviceGetNetworkDefaultGateway,
|
|
onvif.DeviceGetNetworkProtocols,
|
|
onvif.DeviceGetNTP,
|
|
onvif.DeviceGetScopes,
|
|
onvif.MediaGetVideoEncoderConfigurations,
|
|
onvif.MediaGetAudioEncoderConfigurations,
|
|
onvif.MediaGetAudioSources,
|
|
onvif.MediaGetAudioSourceConfigurations:
|
|
b = onvif.StaticResponse(operation)
|
|
|
|
case onvif.DeviceGetCapabilities:
|
|
// important for Hass: Media section
|
|
b = onvif.GetCapabilitiesResponse(r.Host)
|
|
|
|
case onvif.DeviceGetServices:
|
|
b = onvif.GetServicesResponse(r.Host)
|
|
|
|
case onvif.DeviceGetDeviceInformation:
|
|
// important for Hass: SerialNumber (unique server ID)
|
|
b = onvif.GetDeviceInformationResponse("", "go2rtc", app.Version, r.Host)
|
|
|
|
case onvif.ServiceGetServiceCapabilities:
|
|
// important for Hass
|
|
// TODO: check path links to media
|
|
b = onvif.GetMediaServiceCapabilitiesResponse()
|
|
|
|
case onvif.DeviceSystemReboot:
|
|
b = onvif.StaticResponse(operation)
|
|
|
|
time.AfterFunc(time.Second, func() {
|
|
os.Exit(0)
|
|
})
|
|
|
|
case onvif.MediaGetVideoSources:
|
|
b = onvif.GetVideoSourcesResponse(streams.GetAllNames())
|
|
|
|
case onvif.MediaGetProfiles:
|
|
// important for Hass: H264 codec, width, height
|
|
b = onvif.GetProfilesResponse(streams.GetAllNames())
|
|
|
|
case onvif.MediaGetProfile:
|
|
token := onvif.FindTagValue(b, "ProfileToken")
|
|
b = onvif.GetProfileResponse(token)
|
|
|
|
case onvif.MediaGetVideoSourceConfigurations:
|
|
// important for Happytime Onvif Client
|
|
b = onvif.GetVideoSourceConfigurationsResponse(streams.GetAllNames())
|
|
|
|
case onvif.MediaGetVideoSourceConfiguration:
|
|
token := onvif.FindTagValue(b, "ConfigurationToken")
|
|
b = onvif.GetVideoSourceConfigurationResponse(token)
|
|
|
|
case onvif.MediaGetStreamUri:
|
|
host, _, err := net.SplitHostPort(r.Host)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
uri := "rtsp://" + host + ":" + rtsp.Port + "/" + onvif.FindTagValue(b, "ProfileToken")
|
|
b = onvif.GetStreamUriResponse(uri)
|
|
|
|
case onvif.MediaGetSnapshotUri:
|
|
uri := "http://" + r.Host + "/api/frame.jpeg?src=" + onvif.FindTagValue(b, "ProfileToken")
|
|
b = onvif.GetSnapshotUriResponse(uri)
|
|
|
|
default:
|
|
http.Error(w, "unsupported operation", http.StatusBadRequest)
|
|
log.Warn().Msgf("[onvif] unsupported operation: %s", operation)
|
|
log.Debug().Msgf("[onvif] unsupported request:\n%s", b)
|
|
return
|
|
}
|
|
|
|
log.Trace().Msgf("[onvif] server response:\n%s", b)
|
|
|
|
w.Header().Set("Content-Type", "application/soap+xml; charset=utf-8")
|
|
if _, err = w.Write(b); err != nil {
|
|
log.Error().Err(err).Caller().Send()
|
|
}
|
|
}
|
|
|
|
func apiOnvif(w http.ResponseWriter, r *http.Request) {
|
|
src := r.URL.Query().Get("src")
|
|
|
|
var items []*api.Source
|
|
|
|
if src == "" {
|
|
urls, err := onvif.DiscoveryStreamingURLs()
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
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.Source{Name: u.Host, URL: u.String()})
|
|
}
|
|
} else {
|
|
client, err := onvif.NewClient(src)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
if l := log.Trace(); l.Enabled() {
|
|
b, _ := client.MediaRequest(onvif.MediaGetProfiles)
|
|
l.Msgf("[onvif] src=%s profiles:\n%s", src, b)
|
|
}
|
|
|
|
name, err := client.GetName()
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
tokens, err := client.GetProfilesTokens()
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
for i, token := range tokens {
|
|
items = append(items, &api.Source{
|
|
Name: name + " stream" + strconv.Itoa(i),
|
|
URL: src + "?subtype=" + token,
|
|
})
|
|
}
|
|
|
|
if len(tokens) > 0 && client.HasSnapshots() {
|
|
items = append(items, &api.Source{
|
|
Name: name + " snapshot",
|
|
URL: src + "?subtype=" + tokens[0] + "&snapshot",
|
|
})
|
|
}
|
|
}
|
|
|
|
api.ResponseSources(w, items)
|
|
}
|