wip
This commit is contained in:
110
server/ingest/discovery.go
Normal file
110
server/ingest/discovery.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package ingest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/url"
|
||||
|
||||
"github.com/gowvp/onvif"
|
||||
"github.com/gowvp/onvif/media"
|
||||
mediaSdk "github.com/gowvp/onvif/sdk/media"
|
||||
onvifXsd "github.com/gowvp/onvif/xsd/onvif"
|
||||
)
|
||||
|
||||
type OnvifProfile struct {
|
||||
Name onvifXsd.Name
|
||||
Token onvifXsd.ReferenceToken
|
||||
URI *url.URL
|
||||
}
|
||||
|
||||
// GetOnvifProfiles retrieves all available ONVIF stream profiles for a camera.
|
||||
//
|
||||
// Note that the result is very limited since many cameras provide extremely incomplete data,
|
||||
// so it's safest to assume that we only have the bare minimum.
|
||||
func GetOnvifProfiles(ctx context.Context, host, username, password string) ([]OnvifProfile, error) {
|
||||
dev, err := onvif.NewDevice(onvif.DeviceParams{
|
||||
Xaddr: host,
|
||||
Username: username,
|
||||
Password: password,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create ONVIF device: %w", err)
|
||||
}
|
||||
|
||||
// Get available profiles
|
||||
profiles, err := mediaSdk.Call_GetProfiles(ctx, dev, media.GetProfiles{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get ONVIF profiles: %w", err)
|
||||
}
|
||||
|
||||
// Get available URIs for each profile
|
||||
onvifProfiles := make([]OnvifProfile, 0, len(profiles.Profiles))
|
||||
for _, p := range profiles.Profiles {
|
||||
uri, err := mediaSdk.Call_GetStreamUri(ctx, dev, media.GetStreamUri{
|
||||
StreamSetup: onvifXsd.StreamSetup{
|
||||
Stream: onvifXsd.StreamType("RTP-Unicast"),
|
||||
Transport: onvifXsd.Transport{
|
||||
Protocol: onvifXsd.TransportProtocol("TCP"),
|
||||
},
|
||||
},
|
||||
ProfileToken: p.Token,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get stream URI for profile %s: %w", p.Token, err)
|
||||
}
|
||||
if uri.MediaUri.Uri != "" {
|
||||
u, err := url.Parse(string(uri.MediaUri.Uri))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse stream URI for profile %s: %w", p.Token, err)
|
||||
}
|
||||
onvifProfiles = append(onvifProfiles, OnvifProfile{
|
||||
Name: p.Name,
|
||||
Token: p.Token,
|
||||
URI: u,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return onvifProfiles, nil
|
||||
}
|
||||
|
||||
type StreamStats struct {
|
||||
FPS float64
|
||||
Width int
|
||||
Height int
|
||||
}
|
||||
|
||||
// GetStreamStats starts a temporary pipeline to retrieve some basic (pre-decoding) information about the RTSP stream.
|
||||
func GetStreamStats(ctx context.Context, log *slog.Logger, rtspUrl *url.URL, profileToken string) (stats StreamStats, err error) {
|
||||
p, err := CreatePipeline(log)
|
||||
if err != nil {
|
||||
return stats, fmt.Errorf("failed to create pipeline: %w", err)
|
||||
}
|
||||
if err := p.addRtspSource(rtspUrl); err != nil {
|
||||
return stats, fmt.Errorf("failed to add RTSP source: %w", err)
|
||||
}
|
||||
statsChan, err := p.addStatsBranch()
|
||||
if err != nil {
|
||||
return stats, fmt.Errorf("failed to add stats branch: %w", err)
|
||||
}
|
||||
|
||||
statsFound := false
|
||||
runCtx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
go func() {
|
||||
select {
|
||||
case stats = <-statsChan:
|
||||
statsFound = true
|
||||
cancel()
|
||||
case <-runCtx.Done():
|
||||
}
|
||||
}()
|
||||
if err := p.Run(runCtx); err != nil {
|
||||
return stats, fmt.Errorf("failed to run pipeline: %w", err)
|
||||
}
|
||||
if !statsFound {
|
||||
return stats, fmt.Errorf("failed to retrieve stream stats: no stats received")
|
||||
}
|
||||
return stats, nil
|
||||
}
|
||||
Reference in New Issue
Block a user