Files
Strix/internal/camera/discovery/onvif_simple.go
T
eduard256 f80f7ab314 Add Strix camera discovery system with comprehensive database
This commit adds the complete Strix IP camera stream discovery system:
- Go-based API server with SSE support for real-time updates
- 3,600+ camera brand database with stream URL patterns
- Intelligent fuzzy search across camera models
- ONVIF discovery and stream validation
- RESTful API with health check, camera search, and stream discovery
- Makefile for building and deployment
- Comprehensive README documentation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-28 17:45:04 +03:00

140 lines
4.1 KiB
Go

package discovery
import (
"context"
"fmt"
"net/url"
"strings"
"github.com/strix-project/strix/internal/models"
)
// ONVIFDiscovery handles ONVIF device discovery and stream detection
type ONVIFDiscovery struct {
logger interface{ Debug(string, ...any); Error(string, error, ...any) }
}
// NewONVIFDiscovery creates a new ONVIF discovery instance
func NewONVIFDiscovery(logger interface{ Debug(string, ...any); Error(string, error, ...any) }) *ONVIFDiscovery {
return &ONVIFDiscovery{
logger: logger,
}
}
// DiscoverStreamsForIP discovers all possible streams for a given IP
func (o *ONVIFDiscovery) DiscoverStreamsForIP(ctx context.Context, ip, username, password string) ([]models.DiscoveredStream, error) {
// Clean IP (remove port if present)
if idx := strings.IndexByte(ip, ':'); idx > 0 {
ip = ip[:idx]
}
// Return common RTSP streams as we can't use complex ONVIF due to API changes
streams := o.getCommonRTSPStreams(ip, username, password)
o.logger.Debug("generated common RTSP streams", "count", len(streams))
return streams, nil
}
// getCommonRTSPStreams returns common RTSP stream URLs
func (o *ONVIFDiscovery) getCommonRTSPStreams(ip, username, password string) []models.DiscoveredStream {
// Common RTSP paths that work with many cameras
commonPaths := []struct {
path string
notes string
}{
{"/stream1", "Common main stream"},
{"/stream2", "Common sub stream"},
{"/ch0", "Thingino main"},
{"/ch1", "Thingino sub"},
{"/live/main", "ONVIF standard main"},
{"/live/sub", "ONVIF standard sub"},
{"/Streaming/Channels/101", "Hikvision main"},
{"/Streaming/Channels/102", "Hikvision sub"},
{"/cam/realmonitor?channel=1&subtype=0", "Dahua main"},
{"/cam/realmonitor?channel=1&subtype=1", "Dahua sub"},
{"/h264/main", "Generic H264 main"},
{"/h264/sub", "Generic H264 sub"},
{"/media/video1", "Axis main"},
{"/media/video2", "Axis sub"},
{"/videoMain", "Foscam main"},
{"/videoSub", "Foscam sub"},
{"/11", "Simple numeric main"},
{"/12", "Simple numeric sub"},
{"/user=admin_password=tlJwpbo6_channel=1_stream=0.sdp", "Dahua alternative"},
{"/live.sdp", "Generic live"},
{"/stream", "Generic stream"},
{"/video.h264", "Generic H264"},
{"/live/0/MAIN", "Alternative main"},
{"/live/0/SUB", "Alternative sub"},
{"/MediaInput/h264", "Alternative H264"},
{"/0/video0", "Alternative video0"},
{"/0/video1", "Alternative video1"},
}
var streams []models.DiscoveredStream
for _, cp := range commonPaths {
var streamURL string
if username != "" && password != "" {
streamURL = fmt.Sprintf("rtsp://%s:%s@%s:554%s", url.QueryEscape(username), url.QueryEscape(password), ip, cp.path)
} else {
streamURL = fmt.Sprintf("rtsp://%s:554%s", ip, cp.path)
}
streams = append(streams, models.DiscoveredStream{
URL: streamURL,
Type: "FFMPEG",
Protocol: "rtsp",
Port: 554,
Working: false, // Will be tested later
Metadata: map[string]interface{}{
"source": "common",
"notes": cp.notes,
},
})
}
// Add some HTTP snapshot URLs too
httpPaths := []struct {
path string
notes string
}{
{"/snapshot.jpg", "Common snapshot"},
{"/snap.jpg", "Alternative snapshot"},
{"/image/jpeg.cgi", "CGI snapshot"},
{"/cgi-bin/snapshot.cgi", "CGI bin snapshot"},
{"/jpg/image.jpg", "JPEG image"},
{"/tmpfs/auto.jpg", "Tmpfs snapshot"},
{"/axis-cgi/jpg/image.cgi", "Axis snapshot"},
{"/cgi-bin/viewer/video.jpg", "Viewer snapshot"},
{"/Streaming/channels/1/picture", "Hikvision snapshot"},
{"/onvif/snapshot", "ONVIF snapshot"},
}
for _, hp := range httpPaths {
var streamURL string
if username != "" && password != "" {
// For HTTP, we'll rely on Basic Auth instead of URL embedding
streamURL = fmt.Sprintf("http://%s%s", ip, hp.path)
} else {
streamURL = fmt.Sprintf("http://%s%s", ip, hp.path)
}
streams = append(streams, models.DiscoveredStream{
URL: streamURL,
Type: "JPEG",
Protocol: "http",
Port: 80,
Working: false, // Will be tested later
Metadata: map[string]interface{}{
"source": "common",
"notes": hp.notes,
"username": username,
"password": password,
},
})
}
return streams
}