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:
Levi Woodard
2026-07-01 21:55:25 -05:00
parent 780396076d
commit 8d95c944bd
10 changed files with 287 additions and 70 deletions

View File

@@ -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