8. Convenience Functions & Helpers¶
8.1 Device Discovery & Management¶
Core functions for discovering, enumerating, and identifying camera devices on the system.
list_devices() - enumerate all cameras¶
Returns list of all camera devices recognized by DirectShow. Primary discovery function.
def list_devices() -> list[Device]:
"""Get list of available camera devices."""
Returns: List of Device objects with name, path, and is_valid() method.
Usage:
devices = duvc.list_devices()
print(f"Found {len(devices)} camera(s)")
for dev in devices:
print(f" {dev.name} - {dev.path}")
devices() - alias for list_devices()¶
Recommended shorter alias for list_devices(). Preferred for cleaner code.
def devices() -> list[Device]:
"""Get list of available camera devices (alias)."""
Usage:
# Preferred
for dev in duvc.devices():
print(dev.name)
# Equivalent to
for dev in duvc.list_devices():
print(dev.name)
find_device_by_name() - search by name substring¶
Find first device matching case-insensitive substring search. Raises exception if not found.
def find_device_by_name(
name: str,
devices_list: list[Device] | None = None
) -> Device:
"""
Find first device with name containing search string.
Args:
name: Search string (case-insensitive)
devices_list: Optional pre-fetched device list (avoids re-enumeration)
Returns:
Device if found
Raises:
DeviceNotFoundError: If no matching device found
"""
Usage:
# Find by substring
try:
logitech = duvc.find_device_by_name("Logitech")
print(f"Found: {logitech.name}")
except duvc.DeviceNotFoundError as e:
print(f"Not found: {e}")
# Reuse enumeration
device_list = duvc.devices()
cam1 = duvc.find_device_by_name("USB", device_list)
cam2 = duvc.find_device_by_name("HD", device_list) # No re-enumeration
find_devices_by_name() - search all matches¶
Find all devices matching case-insensitive substring. Returns empty list if none found (never raises).
def find_devices_by_name(
name: str,
devices_list: list[Device] | None = None
) -> list[Device]:
"""
Find all devices with name containing search string.
Args:
name: Search string (case-insensitive)
devices_list: Optional pre-fetched device list
Returns:
List of matching devices (empty if none found)
"""
Usage:
# Find all USB cameras
usb_cameras = duvc.find_devices_by_name("USB")
print(f"Found {len(usb_cameras)} USB camera(s)")
for cam in usb_cameras:
print(f" {cam.name}")
# Find all Logitech devices
logitechs = duvc.find_devices_by_name("logitech")
if not logitechs:
print("No Logitech cameras found")
find_device_by_path() - lookup by Windows device path¶
Find device by Windows device path. Case-insensitive matching. Useful for reliably reconnecting to a specific camera across application restarts.
def find_device_by_path(path: str) -> Device:
"""
Find device by Windows device path.
Args:
path: Windows device path (case-insensitive, e.g., "\\?\\USB#VID_046D&PID_082D...")
Returns:
Device if found
Raises:
Exception: If path not found or invalid (generalized exception for broad error handling)
"""
Usage:
import duvc_ctl as duvc
# Save device path on first run
devices = duvc.list_devices()
if devices:
saved_path = devices[0].path
print(f"Device path: {saved_path}")
# Later session - reconnect using saved path
try:
device = duvc.find_device_by_path(saved_path)
print(f"Reconnected to: {device.name}")
except Exception as e:
print(f"Device not found at path: {e}")
# Case-insensitive - these are equivalent
device1 = duvc.find_device_by_path(saved_path)
device2 = duvc.find_device_by_path(saved_path.upper())
device3 = duvc.find_device_by_path(saved_path.lower())
Multi-camera scenario - maintain stable device references:
import duvc_ctl as duvc
import json
# Discovery and save
camera_map = {}
for dev in duvc.list_devices():
camera_map[dev.name] = dev.path
# Save to file for next run
with open("camera_paths.json", "w") as f:
json.dump(camera_map, f)
# Later - reconnect all
loaded_paths = json.load(open("camera_paths.json"))
for name, path in loaded_paths.items():
try:
device = duvc.find_device_by_path(path)
camera = duvc.CameraController(device=device)
print(f"✅ Connected: {camera.device_name}")
except Exception:
print(f"❌ {name} not found at {path}")
Path format: Windows device paths follow the pattern \\?\\USB#VID_XXXX&PID_XXXX#SERIAL.... These are stable identifiers that persist across sessions.
iter_devices() - lazy device iteration¶
Generator yielding devices one at a time. Memory-efficient for large device counts.
def iter_devices() -> Generator[Device, None, None]:
"""Yield available video devices one at a time."""
Usage:
# Iterate without loading all into memory
for device in duvc.iter_devices():
print(device.name)
if device.name == "Target":
break # Stop early if found
# First device only
first_device = next(duvc.iter_devices(), None)
if first_device:
print(f"First camera: {first_device.name}")
iter_connected_devices() - filter connected only¶
Generator yielding only devices that are currently connected and accessible. Filters disconnected/stale devices.
def iter_connected_devices() -> Generator[Device, None, None]:
"""Yield only connected devices."""
Usage:
# Only connected cameras
for device in duvc.iter_connected_devices():
print(f"Connected: {device.name}")
try:
camera = duvc.Camera(device)
# Use camera
except duvc.DeviceNotFoundError:
print(f"Device {device.name} became unavailable")
list_cameras() - Pythonic device listing¶
Alias for device enumeration in CameraController context. Returns same as list_devices().
def list_cameras() -> list[Device]:
"""Get list of available cameras (Pythonic API alias)."""
Usage:
# CameraController context
cameras = duvc.list_cameras()
for cam_device in cameras:
camera = duvc.CameraController(cam_device)
print(f"{camera.name}: brightness={camera.brightness}")
find_camera() - find and open camera¶
Find device by substring and return opened CameraController. Combines find + open.
def find_camera(name: str) -> CameraController:
"""
Find and open camera by name substring.
Args:
name: Search string (case-insensitive)
Returns:
Opened CameraController instance
Raises:
DeviceNotFoundError: If no matching device found
DeviceBusyError: If device in use
"""
Usage:
# One-step find and open
try:
camera = duvc.find_camera("Logitech")
print(f"Opened: {camera.name}")
camera.set_brightness(150)
except duvc.DeviceNotFoundError:
print("Camera not found")
except duvc.DeviceBusyError:
print("Camera in use")
get_device_info() - comprehensive device analysis¶
Query all device properties, capabilities, and current values. Non-throwing: returns error details for failed properties.
def get_device_info(device: Device) -> DeviceInfo:
"""
Collect property metadata for a device.
Queries device capabilities and reads all property values, ranges,
and current settings. Failed property reads are captured with error
details rather than raising exceptions.
Args:
device: Target device
Returns:
DeviceInfo dict with:
- name: str
- path: str
- connected: bool
- camera_properties: dict[str, PropertyInfo]
- video_properties: dict[str, PropertyInfo]
- error: str | None
"""
Usage:
device = duvc.devices()[^0]
info = duvc.get_device_info(device)
print(f"Device: {info['name']}")
print(f"Connected: {info['connected']}")
# Inspect properties
for prop_name, prop_info in info['camera_properties'].items():
if prop_info['supported']:
current = prop_info['current']
range_info = prop_info['range']
print(f"{prop_name}: {current['value']} ({range_info['min']}-{range_info['max']})")
else:
print(f"{prop_name}: Not supported - {prop_info['error']}")
# Video properties
for prop_name, prop_info in info['video_properties'].items():
if prop_info['supported']:
print(f"{prop_name}: {prop_info['current']['value']}")
get_camera_info() - alias for get_device_info()¶
Pythonic alias for get_device_info(). Identical functionality.
def get_camera_info(device: Device) -> DeviceInfo:
"""Alias for get_device_info() (Pythonic API)."""
Device support matrix reference¶
Different cameras support different property sets. Query before use to avoid exceptions.
Common property support patterns:
Camera Type |
Pan/Tilt |
Zoom |
Focus |
Exposure |
WhiteBalance |
Brightness |
|---|---|---|---|---|---|---|
Webcam (basic) |
❌ |
❌ |
❌ |
✅ |
✅ |
✅ |
HD Webcam |
❌ |
✅ |
✅ (digital) |
✅ |
✅ |
✅ |
PTZ Camera |
✅ |
✅ |
✅ |
✅ |
✅ |
✅ |
Conference Cam |
✅ |
✅ |
✅ |
✅ |
✅ |
✅ |
Security Cam |
✅ |
✅ |
❌ |
✅ |
✅ |
✅ |
Usage pattern:
# Query capabilities before use
device = duvc.devices()[^0]
info = duvc.get_device_info(device)
# Check if property supported
if info['camera_properties']['Pan']['supported']:
camera = duvc.CameraController(device)
camera.set_pan(45)
else:
print("Pan not supported on this device")
# Batch capability check
supported_props = [
name for name, prop_info in info['video_properties'].items()
if prop_info['supported']
]
print(f"Supported video properties: {supported_props}")
8.2 Device Context Managers¶
Context managers for automatic device cleanup and safe resource management. Two levels: DeviceContextManager for direct core Camera access, and context-aware properties on CameraController.
DeviceContextManager class¶
Wraps core Camera for automatic cleanup via with statement. Direct access to low-level Result types.
Methods:
__enter__()– Opens device, returns coreCameraobject.__exit__(exctype, excval, exctb)– Closes device, releases resources.is_closed(property) – Check if context has been closed.
Usage:
from duvc_ctl import open_device_context
device = duvc.devices()[^0]
# Automatic cleanup with context manager
with duvc.open_device_context(device) as camera:
result = camera.get(duvc.VidProp.Brightness)
if result.is_ok():
print(f"Brightness: {result.value().value}")
# Camera auto-closed on exit, even if exception occurs
# Camera is now closed; further operations fail
Exception handling:
device = duvc.devices()[^0]
try:
with duvc.open_device_context(device) as camera:
# Operations here
result = camera.get(duvc.VidProp.Contrast)
if not result.is_ok():
raise Exception(result.error().description())
except RuntimeError as e:
if "already open" in str(e):
print("Device already in use")
else:
raise
# Camera is properly closed even if exception raised
open_device_context() - device context factory¶
Creates DeviceContextManager for a specific device. Low-level control with explicit cleanup.
def open_device_context(device: Device) -> DeviceContextManager:
"""
Create context manager for direct core Camera access.
Args:
device: Device to connect to
Returns:
Context manager yielding core Camera object
Raises:
RuntimeError: If device cannot be opened
"""
Usage:
device = duvc.devices()[^0]
# Single operation with automatic cleanup
with duvc.open_device_context(device) as camera:
brightness = camera.get(duvc.VidProp.Brightness)
camera.set(duvc.VidProp.Brightness, duvc.PropSetting(150))
# Multiple operations with same camera
with duvc.open_device_context(device) as camera:
for prop in [duvc.VidProp.Brightness, duvc.VidProp.Contrast]:
result = camera.get(prop)
if result.is_ok():
print(f"{prop}: {result.value().value}")
open_device_by_name_context() - named device context¶
Finds device by name substring, then opens as context manager. Combines discovery + opening in one call.
def open_device_by_name_context(device_name: str) -> DeviceContextManager:
"""
Create context manager for device access by name.
Args:
device_name: Device name or partial match (case-insensitive)
Returns:
Context manager yielding core Camera object
Raises:
DeviceNotFoundError: If no matching device found
RuntimeError: If device cannot be opened
"""
Usage:
# Find by substring and open automatically
try:
with duvc.open_device_by_name_context("Logitech") as camera:
brightness = camera.get(duvc.VidProp.Brightness)
print(f"Brightness: {brightness.value().value}")
except duvc.DeviceNotFoundError:
print("Logitech camera not found")
Case-insensitive substring matching¶
Both context managers and discovery functions use case-insensitive substring matching for device names. Allows flexible search without exact names.
Matching behavior:
# Device actual name: "Logitech USB HD Webcam"
# All these match:
duvc.open_device_by_name_context("logitech") # ✓ Substring
duvc.open_device_by_name_context("LOGITECH") # ✓ Case insensitive
duvc.open_device_by_name_context("usb hd") # ✓ Substring
duvc.open_device_by_name_context("webcam") # ✓ Suffix match
duvc.open_device_by_name_context("HD Web") # ✓ Non-consecutive substring
# These don't match:
duvc.open_device_by_name_context("Sony") # ✗ Different device
duvc.open_device_by_name_context("cam2") # ✗ Not substring
Matching is done with:
if search_string.lower() in device_name.lower():
# Match found
Multi-device scenarios:
# Device list:
# [^0] "Logitech USB HD Webcam"
# [^1] "Logitech C920"
# [^2] "Built-in Camera"
# Search returns FIRST match
with duvc.open_device_by_name_context("Logitech") as camera:
# Opens device [^0] "Logitech USB HD Webcam"
pass
# Be specific to target single device
with duvc.open_device_by_name_context("C920") as camera:
# Opens device [^1] "Logitech C920"
pass
# Find all matches first, then open
cameras = duvc.find_devices_by_name("Logitech")
for device in cameras:
with duvc.open_device_context(device) as camera:
print(device.name)
CameraController also uses substring matching:
# Pythonic API with automatic matching
camera = duvc.CameraController(device_name="Logitech")
# Finds first device containing "logitech" (case-insensitive)
# Also works with device index
camera = duvc.CameraController(device_index=0)
# Opens first device by index
8.3 Property & Connection Helpers¶
Utility functions for safe property operations, connection verification, and batch management.
reset_device_to_defaults() - restore factory settings¶
Reset all device properties to their default values. Queries property ranges for defaults, then sets each. Individual failures don’t stop remaining properties.
def reset_device_to_defaults(device: Device) -> dict[str, bool]:
"""
Reset all supported properties to device defaults.
Args:
device: Target device
Returns:
Dict mapping property names (e.g., "Brightness", "Contrast") to success status
"""
Usage:
device = duvc.devices()[^0]
# Restore all to defaults
results = duvc.reset_device_to_defaults(device)
# Check which reset successfully
for prop_name, success in results.items():
status = "✓" if success else "✗"
print(f"{status} {prop_name}")
# Track failed properties
failed = {name for name, success in results.items() if not success}
if failed:
print(f"Failed to reset: {failed}")
get_supported_properties() - capability query¶
Query all supported camera and video properties for a device. Non-throwing: captures errors.
def get_supported_properties(device: Device) -> dict[str, list[str]]:
"""
Get lists of supported properties.
Args:
device: Target device
Returns:
Dict with keys:
- "camera": List of supported camera property names
- "video": List of supported video property names
"""
Usage:
device = duvc.devices()[^0]
supported = duvc.get_supported_properties(device)
print("Camera properties:")
for prop in supported["camera"]:
print(f" - {prop}")
print("Video properties:")
for prop in supported["video"]:
print(f" - {prop}")
# Filter before operations
if "Pan" in supported["camera"]:
camera = duvc.CameraController(device)
camera.set_pan(45)
else:
print("Pan not supported")
set_property_safe() - error-free property setting¶
Set property with validation and error capture. Returns status + error message without raising. Type-safe enum checking.
def set_property_safe(
device: Device,
domain: str,
property_enum: CamProp | VidProp,
value: int,
mode: str = "manual"
) -> tuple[bool, str]:
"""
Set property with validation and error reporting.
Args:
device: Target device
domain: "cam" or "vid" (case-insensitive)
property_enum: CamProp or VidProp enum
value: Property value
mode: "auto" or "manual" (default)
Returns:
(success: bool, error_message: str)
error_message is empty if successful
"""
Usage:
device = duvc.devices()[^0]
# Safe property set (no exceptions)
success, error = duvc.set_property_safe(
device,
"vid",
duvc.VidProp.Brightness,
150,
mode="manual"
)
if success:
print("Brightness set successfully")
else:
print(f"Failed: {error}")
# Handle gracefully
# Batch safe sets
props_to_set = [
("vid", duvc.VidProp.Brightness, 100),
("vid", duvc.VidProp.Contrast, 80),
("cam", duvc.CamProp.Pan, 45),
]
for domain, prop_enum, value in props_to_set:
success, error = duvc.set_property_safe(device, domain, prop_enum, value)
if not success:
print(f"Skipping {prop_enum}: {error}")
get_property_safe() - error-free property reading¶
Get property with validation and error capture. Returns status + value + error message. No exceptions.
def get_property_safe(
device: Device,
domain: str,
property_enum: CamProp | VidProp
) -> tuple[bool, PropSetting | None, str]:
"""
Get property with validation and error reporting.
Args:
device: Target device
domain: "cam" or "vid" (case-insensitive)
property_enum: CamProp or VidProp enum
Returns:
(success: bool, setting: PropSetting | None, error_message: str)
error_message is empty if successful
"""
Usage:
device = duvc.devices()[^0]
# Safe property read (no exceptions)
success, setting, error = duvc.get_property_safe(
device,
"vid",
duvc.VidProp.Brightness
)
if success:
print(f"Brightness: {setting.value} (mode: {setting.mode})")
else:
print(f"Failed to read: {error}")
# Batch safe reads
props_to_read = [
("vid", duvc.VidProp.Brightness),
("vid", duvc.VidProp.Contrast),
("cam", duvc.CamProp.Pan),
]
current_state = {}
for domain, prop_enum in props_to_read:
success, setting, error = duvc.get_property_safe(device, domain, prop_enum)
if success:
current_state[str(prop_enum)] = setting.value
else:
print(f"Skipping read: {error}")
is_device_connected() - connection status check¶
Check if device is currently accessible and connected. Non-throwing: returns bool.
def is_device_connected(device: Device) -> bool:
"""
Check if device is currently connected and accessible.
Args:
device: Target device
Returns:
True if device accessible, False if disconnected or inaccessible
"""
Usage:
device = duvc.devices()[^0]
if duvc.is_device_connected(device):
print(f"{device.name} is connected")
camera = duvc.CameraController(device)
else:
print(f"{device.name} is disconnected")
# Try to find alternative or reconnect
# Connection monitoring
for device in duvc.devices():
status = "✓" if duvc.is_device_connected(device) else "✗"
print(f"{status} {device.name}")
# Retry pattern
def get_with_retry(device, prop, retries=3):
for attempt in range(retries):
if not duvc.is_device_connected(device):
print(f"Attempt {attempt+1}: Device disconnected")
time.sleep(1)
continue
try:
camera = duvc.CameraController(device)
return camera.get(prop)
except duvc.DuvcError as e:
if attempt < retries - 1:
time.sleep(1)
return None
8.4 Logging Setup & Utilities¶
Configure logging level, enable debug output, and set callback handlers for library events.
setup_logging() - configure log level and callback¶
Set log level and optional callback for all library log messages. Callback receives (level, message).
def setup_logging(
level: LogLevel = LogLevel.Info,
callback: Callable[[LogLevel, str], None] | None = None
) -> None:
"""
Configure library logging.
Args:
level: Minimum log level to capture (default: Info)
callback: Optional function(level, message) called for each log event
"""
LogLevel values: Debug, Info, Warning, Error, Critical
Usage:
import duvc_ctl as duvc
# Set level without callback
duvc.setup_logging(duvc.LogLevel.Debug)
# With custom callback
def my_log_handler(level, message):
level_name = duvc.to_string(level)
timestamp = datetime.datetime.now().isoformat()
print(f"[{timestamp}] {level_name}: {message}")
duvc.setup_logging(duvc.LogLevel.Debug, my_log_handler)
# Log to file
log_file = open("duvc.log", "a")
def file_log_handler(level, message):
level_name = duvc.to_string(level)
log_file.write(f"{level_name}: {message}\n")
log_file.flush()
duvc.setup_logging(duvc.LogLevel.Info, file_log_handler)
# Operations now logged
camera = duvc.CameraController(0)
camera.set_brightness(150) # Triggers log messages
enable_debug_logging() - quick debug output¶
Enable debug-level logging with console output. Convenience function for quick diagnostics.
def enable_debug_logging() -> None:
"""
Enable debug-level logging to console.
Equivalent to setup_logging(LogLevel.Debug, print_handler)
"""
Usage:
import duvc_ctl as duvc
# Single call for debugging
duvc.enable_debug_logging()
# Now see all debug output
devices = duvc.list_devices() # Logs device enumeration
camera = duvc.CameraController(0) # Logs open operation
camera.set_brightness(150) # Logs property operations
# Output example:
# [DEBUG] Enumerating DirectShow devices
# [DEBUG] Found 2 camera(s)
# [DEBUG] Opening device "Logitech USB HD Webcam"
# [DEBUG] Device opened successfully
# [DEBUG] Setting property: Brightness = 150
Typical debug output categories:
Category |
When |
Example |
|---|---|---|
Device Enumeration |
|
“Found N camera(s)”, “Device: name” |
Device Open/Close |
Camera creation |
“Opening device”, “Device opened/closed” |
Property Operations |
|
“Getting Brightness”, “Set Brightness = 150” |
Capabilities Query |
|
“Querying capabilities”, “Supported properties: …” |
Mode Changes |
Property mode changes |
“Setting auto mode”, “Switching to manual” |
Errors |
Failures |
“Failed to open: Permission denied”, “Device disconnected” |