Files
Strix/pkg/camdb/streams.go
T
eduard256 44ab0651cb Add DVRIP protocol support
- Register dvrip stream handler using go2rtc pkg/dvrip
- Add default port 34567 for dvrip scheme in URL builder
2026-04-03 21:01:39 +00:00

193 lines
4.3 KiB
Go

package camdb
import (
"database/sql"
"encoding/base64"
"fmt"
"net/url"
"strconv"
"strings"
)
var defaultPorts = map[string]int{
"rtsp": 554, "rtsps": 322, "http": 80, "https": 443,
"rtmp": 1935, "mms": 554, "rtp": 5004, "bubble": 80,
"dvrip": 34567,
}
// protocols where port must always be explicit in URL (raw TCP dial without default port logic)
var portRequired = map[string]bool{
"bubble": true,
}
type StreamParams struct {
IDs string
IP string
User string
Pass string
Channel int
Ports map[int]bool // nil = no filter
}
type raw struct {
url, protocol string
port int
}
// BuildStreams resolves IDs to full stream URLs with credentials and placeholders substituted
func BuildStreams(db *sql.DB, p *StreamParams) ([]string, error) {
var raws []raw
for _, id := range strings.Split(p.IDs, ",") {
id = strings.TrimSpace(id)
if id == "" {
continue
}
var rows *sql.Rows
var err error
switch {
case strings.HasPrefix(id, "b:"):
brandID := id[2:]
rows, err = db.Query(
"SELECT url, protocol, port FROM streams WHERE brand_id = ?", brandID,
)
case strings.HasPrefix(id, "m:"):
parts := strings.SplitN(id[2:], ":", 2)
if len(parts) != 2 {
return nil, fmt.Errorf("camdb: invalid model id: %s", id)
}
rows, err = db.Query(
`SELECT s.url, s.protocol, s.port
FROM stream_models sm
JOIN streams s ON s.id = sm.stream_id
WHERE s.brand_id = ? AND sm.model = ?`,
parts[0], parts[1],
)
case strings.HasPrefix(id, "p:"):
presetID := id[2:]
rows, err = db.Query(
"SELECT url, protocol, port FROM preset_streams WHERE preset_id = ?", presetID,
)
default:
return nil, fmt.Errorf("camdb: unknown id prefix: %s", id)
}
if err != nil {
return nil, err
}
found := false
for rows.Next() {
var r raw
if err = rows.Scan(&r.url, &r.protocol, &r.port); err != nil {
rows.Close()
return nil, err
}
raws = append(raws, r)
found = true
}
rows.Close()
if !found {
return nil, fmt.Errorf("camdb: not found: %s", id)
}
}
// build full URLs, deduplicate
seen := map[string]bool{}
var streams []string
for _, r := range raws {
if len(streams) >= 20000 {
break
}
port := r.port
if port == 0 {
if p, ok := defaultPorts[r.protocol]; ok {
port = p
} else {
port = 80
}
}
if p.Ports != nil && !p.Ports[port] {
continue
}
u := buildURL(r.protocol, r.url, p.IP, port, p.User, p.Pass, p.Channel)
if seen[u] {
continue
}
seen[u] = true
streams = append(streams, u)
}
return streams, nil
}
// internals
func buildURL(protocol, path, ip string, port int, user, pass string, channel int) string {
path = replacePlaceholders(path, ip, port, user, pass, channel)
var auth string
if user != "" {
auth = url.PathEscape(user) + ":" + url.PathEscape(pass) + "@"
}
host := ip
if p, ok := defaultPorts[protocol]; (!ok || p != port) || portRequired[protocol] {
host = ip + ":" + strconv.Itoa(port)
}
if !strings.HasPrefix(path, "/") {
path = "/" + path
}
return protocol + "://" + auth + host + path
}
func replacePlaceholders(s, ip string, port int, user, pass string, channel int) string {
auth := ""
if user != "" && pass != "" {
auth = base64.StdEncoding.EncodeToString([]byte(user + ":" + pass))
}
// URL-encode credentials for safe use in query parameters
encUser := url.QueryEscape(user)
encPass := url.QueryEscape(pass)
pairs := []string{
"[CHANNEL]", strconv.Itoa(channel),
"[channel]", strconv.Itoa(channel),
"{CHANNEL}", strconv.Itoa(channel),
"{channel}", strconv.Itoa(channel),
"[CHANNEL+1]", strconv.Itoa(channel + 1),
"[channel+1]", strconv.Itoa(channel + 1),
"{CHANNEL+1}", strconv.Itoa(channel + 1),
"{channel+1}", strconv.Itoa(channel + 1),
"[USERNAME]", encUser, "[username]", encUser,
"[USER]", encUser, "[user]", encUser,
"[PASSWORD]", encPass, "[password]", encPass,
"[PASWORD]", encPass, "[pasword]", encPass,
"[PASS]", encPass, "[pass]", encPass,
"[PWD]", encPass, "[pwd]", encPass,
"[WIDTH]", "640", "[width]", "640",
"[HEIGHT]", "480", "[height]", "480",
"[IP]", ip, "[ip]", ip,
"[PORT]", strconv.Itoa(port), "[port]", strconv.Itoa(port),
"[AUTH]", auth, "[auth]", auth,
"[TOKEN]", "", "[token]", "",
}
r := strings.NewReplacer(pairs...)
return r.Replace(s)
}