Per-tile stream quality (high/low), fps cap, latency flags
Cameras carry multiple stream URLs (streams: high/medium/low); each tile picks one via Quality (auto/high/low). Small tiles can pull the low substream to cut Pi decode load. discover --enable-rtsp takes a comma list (high,low), enables each channel, records all enabled ones. TUI: q cycles a tile quality (shown in the tile); discover enables high+low. Player: --framedrop + low-delay demuxer flags; player.max_fps. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"syscall"
|
||||
"text/tabwriter"
|
||||
|
||||
@@ -56,7 +57,6 @@ func daemonCmd() *cobra.Command {
|
||||
|
||||
// discoverCmd queries UniFi Protect and merges cameras into the config.
|
||||
func discoverCmd() *cobra.Command {
|
||||
var lowRes bool
|
||||
var enableRTSP string
|
||||
c := &cobra.Command{
|
||||
Use: "discover",
|
||||
@@ -86,39 +86,57 @@ func discoverCmd() *cobra.Command {
|
||||
return err
|
||||
}
|
||||
|
||||
if enableRTSP != "" {
|
||||
enabled, failed := cl.EnableMissing(ctx, cams, enableRTSP)
|
||||
for _, name := range enabled {
|
||||
fmt.Printf(" + enabled RTSP (%s) on %s\n", enableRTSP, name)
|
||||
}
|
||||
for name, ferr := range failed {
|
||||
fmt.Fprintf(os.Stderr, " ! could not enable RTSP on %s: %v\n", name, ferr)
|
||||
// Qualities to ensure RTSP-enabled, e.g. --enable-rtsp=high,low
|
||||
var wantEnable []string
|
||||
for _, q := range strings.Split(enableRTSP, ",") {
|
||||
if q = strings.TrimSpace(strings.ToLower(q)); q != "" {
|
||||
wantEnable = append(wantEnable, q)
|
||||
}
|
||||
}
|
||||
|
||||
added, updated, skipped := 0, 0, 0
|
||||
for _, cam := range cams {
|
||||
ch := cam.BestEnabledChannel()
|
||||
if lowRes {
|
||||
ch = cam.LowestEnabledChannel()
|
||||
for i := range cams {
|
||||
cam := &cams[i]
|
||||
for _, q := range wantEnable {
|
||||
target := cam.ChannelByPreference(q)
|
||||
if target == nil {
|
||||
continue
|
||||
}
|
||||
if !target.RTSPEnabled || target.RTSPAlias == "" {
|
||||
alias, err := cl.EnableRTSP(ctx, cam.ID, target.ID)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, " ! %s: enable %q failed: %v\n", cam.Name, q, err)
|
||||
continue
|
||||
}
|
||||
target.RTSPEnabled, target.RTSPAlias = true, alias
|
||||
fmt.Printf(" + enabled %s on %s\n", q, cam.Name)
|
||||
}
|
||||
}
|
||||
if ch == nil {
|
||||
fmt.Fprintf(os.Stderr, " ! %s: no RTSP-enabled channel (enable RTSP in Protect)\n", cam.Name)
|
||||
// Record every enabled channel as a selectable quality.
|
||||
streams := map[string]string{}
|
||||
for _, ch := range cam.Channels {
|
||||
if ch.RTSPEnabled && ch.RTSPAlias != "" {
|
||||
streams[strings.ToLower(ch.Name)] = cl.StreamURL(ch.RTSPAlias)
|
||||
}
|
||||
}
|
||||
if len(streams) == 0 {
|
||||
fmt.Fprintf(os.Stderr, " ! %s: no RTSP-enabled channel (try --enable-rtsp=high,low)\n", cam.Name)
|
||||
skipped++
|
||||
continue
|
||||
}
|
||||
url := cl.StreamURL(ch.RTSPAlias)
|
||||
if existing := findByID(cfg, cam.ID); existing != nil {
|
||||
existing.Name, existing.RTSP = cam.Name, url
|
||||
updated++
|
||||
} else if existing := cfg.CameraByName(cam.Name); existing != nil {
|
||||
existing.ID, existing.RTSP = cam.ID, url
|
||||
updated++
|
||||
} else {
|
||||
cfg.Cameras = append(cfg.Cameras, config.Camera{ID: cam.ID, Name: cam.Name, RTSP: url})
|
||||
added++
|
||||
entry := findByID(cfg, cam.ID)
|
||||
if entry == nil {
|
||||
entry = cfg.CameraByName(cam.Name)
|
||||
}
|
||||
fmt.Printf(" ✓ %-24s %dx%d\n", cam.Name, ch.Width, ch.Height)
|
||||
if entry == nil {
|
||||
cfg.Cameras = append(cfg.Cameras, config.Camera{})
|
||||
entry = &cfg.Cameras[len(cfg.Cameras)-1]
|
||||
added++
|
||||
} else {
|
||||
updated++
|
||||
}
|
||||
entry.ID, entry.Name, entry.Streams, entry.RTSP = cam.ID, cam.Name, streams, ""
|
||||
fmt.Printf(" ✓ %-24s [%s]\n", cam.Name, strings.Join(availQual(streams), ","))
|
||||
}
|
||||
if err := config.Save(cfgPath, cfg); err != nil {
|
||||
return err
|
||||
@@ -128,11 +146,21 @@ func discoverCmd() *cobra.Command {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
c.Flags().BoolVar(&lowRes, "low-res", false, "prefer the lowest-resolution substream (good for dense grids)")
|
||||
c.Flags().StringVar(&enableRTSP, "enable-rtsp", "", "enable RTSP in Protect on this channel for cameras that lack it: high|medium|low")
|
||||
c.Flags().StringVar(&enableRTSP, "enable-rtsp", "", "comma-separated channels to enable in Protect and record, e.g. high,low")
|
||||
return c
|
||||
}
|
||||
|
||||
// availQual lists the qualities present in a stream map, high to low.
|
||||
func availQual(streams map[string]string) []string {
|
||||
var out []string
|
||||
for _, q := range config.Qualities {
|
||||
if streams[q] != "" {
|
||||
out = append(out, q)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func findByID(cfg *config.Config, id string) *config.Camera {
|
||||
if id == "" {
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user