Compare commits

...

4 Commits

Author SHA1 Message Date
f45e2d1aaa Merge pull request 'feat: align keyboard LocalID to GlobalID and preserve server credentials' (#2) from feat/keyboard-localid-and-preserve-credentials into main 2026-06-07 08:32:31 +00:00
Docker Config Backup
c54d3a6792 feat: align keyboard LocalID to GlobalID and preserve server credentials
Two operator requirements for MBeg keyboards and server connections:

1. align_keyboard_local_ids(): on every keyboard interface under
   Clients/GeViIO/GeViIO_01, set each video input/output LocalID equal to
   its GlobalID so operators can address cameras/monitors by global id.
   Virtual decoders/servers under GeViIO_Virtual keep their sequential
   local ids. Runs after prune in /api/set/export and /api/batch/build.

2. preserve_server_credentials(): server User/Password are entered by hand
   and must survive re-importing the server list from Excel. Matched by
   alias, an existing non-empty credential overrides the imported value.
   Applied to G-Core and GeViScope servers in /api/batch/build.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 10:32:18 +02:00
3b38a5c6d9 Merge pull request 'fix: clear PTZ flag on all MBeg keyboard video inputs' (#1) from fix/disable-keyboard-ptz into main 2026-05-28 21:34:49 +00:00
Docker Config Backup
18084ec9d7 fix: clear PTZ flag on all MBeg keyboard video inputs
Keyboard interfaces under Clients/GeViIO/GeViIO_01 should never have the
PTZ flag set on their video inputs. Previously PTZ cameras (e.g. 101027)
were exported with PTZ=True on every keyboard, unlike fixed cameras.

Adds geviset.disable_keyboard_ptz() and runs it at the end of the video
input pipeline in both /api/set/export and /api/batch/build. Only the
keyboard interfaces are touched; virtual decoders/servers under
GeViIO_Virtual keep their PTZ flags so PTZ control still works.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-28 23:33:14 +02:00
2 changed files with 92 additions and 0 deletions

View File

@@ -1092,6 +1092,92 @@ def ensure_global_video_inputs(tree, global_ids, ptz_by_id=None):
)
def disable_keyboard_ptz(tree):
"""Force PTZ off for every video input on every MBeg keyboard interface.
Keyboards live directly under Clients/GeViIO/GeViIO_01 (each is an
interface folder with its own VideoInputs). PTZ control on the keyboard
side is never wanted, so the PTZ flag of each video input is cleared.
Virtual decoders/servers under GeViIO_Virtual are left untouched.
"""
geviio = _find_folder(tree, ["Clients", "GeViIO", "GeViIO_01"])
if not geviio:
return 0
cleared = 0
for interface in geviio.get("children", []):
if not isinstance(interface, dict) or interface.get("type") != "folder":
continue
video_inputs = _child_by_name(interface, "VideoInputs")
if video_inputs is None:
continue
for entry in video_inputs.get("children", []):
if entry.get("type") != "folder":
continue
ptz_node = _child_by_name(entry, "PTZ")
if ptz_node is not None and ptz_node.get("value") is not False:
ptz_node["value"] = False
cleared += 1
return cleared
def align_keyboard_local_ids(tree):
"""Make LocalID equal GlobalID for every video input and output on every
MBeg keyboard interface (Clients/GeViIO/GeViIO_01).
Operators address cameras/monitors on the keyboards by their global id,
so the local id must match it. Other interfaces (virtual decoders and
servers under GeViIO_Virtual) keep their own sequential local ids.
"""
geviio = _find_folder(tree, ["Clients", "GeViIO", "GeViIO_01"])
if not geviio:
return 0
changed = 0
for interface in geviio.get("children", []):
if not isinstance(interface, dict) or interface.get("type") != "folder":
continue
for sub in ("VideoInputs", "VideoOutputs"):
container = _child_by_name(interface, sub)
if container is None:
continue
for entry in container.get("children", []):
if entry.get("type") != "folder":
continue
local_node = _child_by_name(entry, "LocalID")
global_node = _child_by_name(entry, "GlobalID")
if local_node is None or global_node is None:
continue
global_id = global_node.get("value")
if isinstance(global_id, int) and local_node.get("value") != global_id:
local_node["value"] = global_id
changed += 1
return changed
def preserve_server_credentials(new_servers, existing_servers):
"""Keep User/Password that were already entered on the matching server.
Server credentials are filled in by hand and must not be wiped when the
server list is re-imported from Excel. Servers are matched by alias; an
existing non-empty user or password overrides the imported value.
"""
by_alias = {}
for server in existing_servers or []:
alias = str(server.get("alias", "")).strip()
if alias:
by_alias[alias] = server
preserved = 0
for server in new_servers or []:
old = by_alias.get(str(server.get("alias", "")).strip())
if not old:
continue
if old.get("user"):
server["user"] = old["user"]
if old.get("password"):
server["password"] = old["password"]
preserved += 1
return preserved
def prune_video_inputs(tree, global_ids):
target_ids = {int(x) for x in global_ids if isinstance(x, int) and x > 0}
if not target_ids:

View File

@@ -110,6 +110,8 @@ async def export_set(payload: dict):
geviset.ensure_global_video_inputs(tree, camera_ids, ptz_by_id)
geviset.ensure_vx3_video_inputs(tree, camera_ids, ptz_by_id)
geviset.prune_video_inputs(tree, camera_ids)
geviset.disable_keyboard_ptz(tree)
geviset.align_keyboard_local_ids(tree)
print(
f"EXPORT camera_ids={len(camera_ids)} contains_101027={101027 in camera_ids}",
flush=True,
@@ -301,6 +303,8 @@ async def build_from_excel(
geviset.ensure_global_video_inputs(tree, camera_ids, ptz_by_id)
geviset.ensure_vx3_video_inputs(tree, camera_ids, ptz_by_id)
geviset.prune_video_inputs(tree, camera_ids)
geviset.disable_keyboard_ptz(tree)
geviset.align_keyboard_local_ids(tree)
if servers and servers.filename:
if not servers.filename.lower().endswith(".xlsx"):
@@ -314,6 +318,8 @@ async def build_from_excel(
s["id"] = str(idx)
bundle_gcore = geviset.extract_gcore(tree)
bundle_gsc = geviset.extract_gsc(tree)
geviset.preserve_server_credentials(gcore_list, bundle_gcore["servers"])
geviset.preserve_server_credentials(gsc_list, bundle_gsc["servers"])
bundle_gcore["servers"] = gcore_list
bundle_gsc["servers"] = gsc_list
geviset.apply_gcore(tree, bundle_gcore)