ATEM: RESI AUDIO SILENT when input 16 has no audio for 30 s; log what the switcher has wrong
2026-10-25 the Resi AJA passed no Dante audio and the panel stayed green (DANTE only sees the card on the network). atem_watch.js now asks the switcher for its Fairlight meters and reports a metered input stuck at the floor (-100 dB); the ATEM key turns red in auto-fix's hours or while a deck records. Differences (audio, camera, silence) are logged when they change, so a fault says what it was. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+19
-3
@@ -1407,7 +1407,7 @@ class Engine:
|
||||
def evaluate(self, snap, now, service_on):
|
||||
ctx = {"now": now, "service": service_on, "any_recording": False, "dante_all": {},
|
||||
"ready_due": in_windows(self.ready_cfg.get("due", []), now), "ready_done": self.ready_done(now),
|
||||
"bypassed": self.bypass_names()[0]}
|
||||
"bypassed": self.bypass_names()[0], "autofix_windows": (self.cfg.get("auto_fix") or {}).get("windows", [])}
|
||||
for t in all_tiles(self.cfg):
|
||||
for chk in t.get("checks", []):
|
||||
if chk["type"] == "dante":
|
||||
@@ -3354,6 +3354,12 @@ def atem_reference(xml_path, compare=None):
|
||||
return {"audio": audio, "camera": camera}
|
||||
|
||||
|
||||
def atem_levels(sil):
|
||||
"""The inputs whose audio is metered for silence ("silence" in config "atem"), for atem_watch.js."""
|
||||
sil = sil or {}
|
||||
return {"levels": {str(k): v for k, v in (sil.get("inputs") or {}).items()}, "silence_seconds": sil.get("seconds", 30)}
|
||||
|
||||
|
||||
def newest(pattern):
|
||||
"""The path matching a pattern like ".../bmd-atem-*/..." with the highest version number."""
|
||||
import glob
|
||||
@@ -3380,7 +3386,8 @@ class AtemWatcher:
|
||||
if not os.path.isabs(xml):
|
||||
xml = os.path.join(here, xml)
|
||||
with open(self.ref_path, "w") as f:
|
||||
json.dump(atem_reference(xml, a.get("compare")), f)
|
||||
json.dump(dict(atem_reference(xml, a.get("compare")), **atem_levels(self.atem.get("silence"))), f)
|
||||
self.said = {} # what each part last said, so a change is logged once
|
||||
|
||||
def run(self):
|
||||
while not self.stop.is_set():
|
||||
@@ -3396,6 +3403,11 @@ class AtemWatcher:
|
||||
data = json.loads(line[5:])
|
||||
data["time"] = time.time()
|
||||
self.snap.atem = data
|
||||
for part in ("audio", "camera", "silence"): # say what differs, not only that it does
|
||||
now_says = data.get(part) or []
|
||||
if data.get("connected") and now_says != self.said.get(part, []):
|
||||
log.info("ATEM %s: %s", part, "; ".join(now_says) or "as it should be")
|
||||
self.said[part] = now_says
|
||||
elif line.startswith("FIXED "):
|
||||
r = json.loads(line[6:])
|
||||
self.fixed[r.get("part")] = r
|
||||
@@ -3436,11 +3448,15 @@ ATEM_WATCHER = None
|
||||
|
||||
|
||||
def check_atem(chk, snap, ctx):
|
||||
"""chk["part"] "audio" or "camera": the switcher must match the restore file."""
|
||||
"""chk["part"] "audio" or "camera": the switcher must match the restore file. "silence": a metered input
|
||||
(Resi) has had no audio at all for a while; only in auto-fix's hours or while a
|
||||
HyperDeck records - at 4 AM the console may simply be off."""
|
||||
a = snap.atem
|
||||
if not a or not a.get("connected") or ctx["now"] - a.get("time", 0) > 30:
|
||||
return [(chk["part"], Result(OK))] # watcher not reporting: no verdict
|
||||
diffs = a.get(chk["part"]) or []
|
||||
if chk["part"] == "silence" and diffs and not (ctx.get("any_recording") or in_windows(ctx.get("autofix_windows") or [], ctx["now"])):
|
||||
return [(chk["part"], Result(OK))]
|
||||
if diffs:
|
||||
return [(chk["part"], Result(ERR, chk["msg"], info="; ".join(diffs[:3]), named=True))]
|
||||
return [(chk["part"], Result(OK))]
|
||||
|
||||
Reference in New Issue
Block a user