The Day Every Camera Changed Its Name
The previous lessons leaned hard on one rule: a device is known by its unique ID, never by its name or its position in a list. Names fail because two identical capture cards report the same name. Positions fail because AVFoundation can order devices differently in two processes. The unique ID was the stable thing.
Then the home Mac rebooted, and a webcam came back with a different unique ID. Nothing about the camera had changed. What had changed was where the system thought it was plugged in: a reboot renumbered the ports on a USB hub. The unique ID, it turned out, was not a name for the device at all.
Reading the ID
For a USB video (UVC) device, AVFoundation's unique ID has a shape you can read: 0x, then the USB location ID of the port, then four hex digits of vendor and four of product. The location is the path through the hubs from the Mac's controller down to the socket. So the ID says "a device of this vendor and product, at this place". Move the camera to another socket, or let a reboot renumber a hub, and the same camera arrives under a new ID.
Oriel's backend/usb.py parses that shape into a UidParts(location, vendor, product). The recorder uses the same three parts for the opposite purpose: when it sends a gimbal command, it looks up the IOKit device that matches all three, so a pan meant for one camera can never reach a different one on another port.
Telling a Moved Device From a New One
If the ID is a place, something else has to say which unit is at the place. Oriel asks the IORegistry, the kernel's tree of attached devices, at the device's location, and reads its USB serial number and product name. Reading the registry never opens the device, so it needs no camera grant and cannot wake anything up.
What the registry says depends on the hardware, and Oriel measured it instead of assuming:
- The capture cards each carry a real, distinct serial. Two cards of one model are told apart for certain.
- One pocket camera reports a placeholder serial (the same string every unit of that model reports) and puts its real serial in the product name instead.
- One webcam reports no serial at all. It can be recognized only by its model.
So the code computes a fingerprint: the serial when it is real (a known list of placeholder serials is ignored), else a serial found at the end of the product name, else nothing. When a new unique ID appears whose fingerprint matches an input that has gone absent, it is that input, moved: the row, its role and its saved camera positions follow it, and the activity log records device_moved with how it was told.
When It Cannot Be Told
A device with no fingerprint is the honest hard case. Oriel matches it to an absent row only when that row is the only one of its model, because then there is nothing to confuse it with. When two absent rows share its model, Oriel does not guess. It registers the device as new and writes plainly in the log which absent rows share its model. A person (or Pippa, through the merge_inputs action) then says by hand that an absent row is the present one. A wrong automatic match would silently move a role, and its camera bookmarks, onto the wrong camera; a visible "I could not tell" costs one action to fix.
Audio Is Keyed Differently
One last twist. A capture card's HDMI audio is a separate audio device with its own unique ID, and that ID carries the card's serial, not its location. So a card's picture and its sound are paired through the IORegistry: from the video ID's location to the serial at that location, then to the one audio device whose ID carries that serial. Never by name, never by order. That pairing is what lets the deck check in track five refuse a webcam's microphone while allowing a deck card's sound.