fix(camera): strict built-in filter both sides

Both the Python AVCapture path and the Swift CameraPreviewLayer
now enforce the same two-step filter :
 1. deviceType == .builtInWideAngleCamera
 2. localizedName must NOT contain 'iPhone', 'GSM', 'Desk View',
    or 'Continuity'

The second clause matters because under some macOS configurations
(Continuity Camera promoted to widescreen, iPhone mirroring),
externally tethered phone cameras can advertise themselves with
the same primary deviceType.

Swift side also logs via NSLog so the picked device shows up in
Console.app even when stdout is buffered.
This commit is contained in:
L'électron rare
2026-05-13 12:01:14 +02:00
parent 20acbeb704
commit a69d096d15
2 changed files with 39 additions and 7 deletions
+14 -3
View File
@@ -69,11 +69,22 @@ def enumerate_devices() -> list[dict]:
return out
_BANNED_NAME_TOKENS = ("iphone", "gsm", "desk view", "continuity")
def find_builtin_device() -> Optional[dict]:
"""Selectionne le BuiltInWideAngleCamera (webcam Mac integree)."""
"""Selectionne la webcam Mac integree :
- deviceType doit etre BuiltInWideAngleCamera
- le nom ne doit contenir aucun de _BANNED_NAME_TOKENS
Evite les pieges Continuity/iPhone/Desk View qui peuvent matcher
BuiltInWideAngleCamera dans certaines configs."""
for info in enumerate_devices():
if "BuiltInWideAngleCamera" in info["type"]:
return info
if "BuiltInWideAngleCamera" not in info["type"]:
continue
name_l = info["name"].lower()
if any(tok in name_l for tok in _BANNED_NAME_TOKENS):
continue
return info
return None
@@ -17,14 +17,36 @@ final class CameraPreviewLayer {
}
func start() -> Bool {
// On enumere TOUS les types puis on filtre durement : nom ne
// contient ni 'iPhone', ni 'GSM', ni 'Desk View', ni
// 'Continuity' (cas Continuity Camera 13+) et deviceType ==
// builtInWideAngleCamera. Ca evite tous les pieges.
let discovery = AVCaptureDevice.DiscoverySession(
deviceTypes: [.builtInWideAngleCamera],
deviceTypes: [.builtInWideAngleCamera,
.continuityCamera,
.external,
.deskViewCamera],
mediaType: .video,
position: .unspecified)
guard let device = discovery.devices.first else {
print("No built-in camera found")
NSLog("AV-Live-Body: %d cameras detected", discovery.devices.count)
for d in discovery.devices {
NSLog("AV-Live-Body: - %@ type=%@",
d.localizedName, d.deviceType.rawValue)
}
let banned = ["iPhone", "GSM", "Desk View", "Continuity"]
let device = discovery.devices.first { d in
guard d.deviceType == .builtInWideAngleCamera else { return false }
let name = d.localizedName
for b in banned where name.localizedCaseInsensitiveContains(b) {
return false
}
return true
}
guard let device = device else {
NSLog("AV-Live-Body: no Mac built-in camera found")
return false
}
NSLog("AV-Live-Body: picked '%@'", device.localizedName)
do {
let input = try AVCaptureDeviceInput(device: device)
guard session.canAddInput(input) else {
@@ -40,7 +62,6 @@ final class CameraPreviewLayer {
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
self?.session.startRunning()
}
print("CameraPreviewLayer running on '\(device.localizedName)'")
return true
}