Last active
March 6, 2024 04:23
-
-
Save debakarr/6cd22fe5bf980ae8fa600d46b082af71 to your computer and use it in GitHub Desktop.
Windows studio access using API in Python
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import asyncio | |
import uuid | |
from ctypes import c_uint32, c_uint64, c_char, Structure, sizeof | |
from enum import Enum | |
from winsdk.windows.media.devices import MediaDevice | |
from winsdk.windows.devices.enumeration import DeviceInformation, Panel | |
from winsdk.windows.media.capture import ( | |
MediaCapture, | |
MediaCaptureInitializationSettings, | |
MediaCaptureSharingMode, | |
MediaCaptureMemoryPreference, | |
StreamingCaptureMode, | |
) | |
from winsdk.windows.media.capture.frames import MediaFrameSourceGroup | |
import winsdk.windows.foundation as wf | |
# GUID equivalent | |
KSPROPERTYSETID_ExtendedCameraControl = uuid.UUID( | |
"1CB79112-C0D2-4213-9CA6-CD4FDB927972" | |
) | |
KSCAMERA_EXTENDEDPROP_FILTERSCOPE = 0xFFFFFFFF | |
DEVPKEY_DeviceInterface_IsWindowsCameraEffectAvailable = ( | |
"{6EDC630D-C0D2-4213-9CA6-CD4FDB927972} 4" | |
) | |
# Enums | |
class KsPropertyKind(Enum): | |
KSPROPERTY_TYPE_GET = 0x00000001 | |
KSPROPERTY_TYPE_SET = 0x00000002 | |
# Structures | |
class KSPROPERTY(Structure): | |
_fields_ = [("Set", c_char * 16), ("Id", c_uint32), ("Flags", c_uint32)] | |
class KSCAMERA_EXTENDEDPROP_HEADER(Structure): | |
_fields_ = [ | |
("Version", c_uint32), | |
("PinId", c_uint32), | |
("Size", c_uint32), | |
("Result", c_uint32), | |
("Flags", c_uint64), | |
("Capability", c_uint64), | |
] | |
class KSCAMERA_EXTENDEDPROP_VALUE(Structure): | |
_fields_ = [("Value", c_uint64)] | |
class KsBasicCameraExtendedPropPayload(Structure): | |
_fields_ = [ | |
("header", KSCAMERA_EXTENDEDPROP_HEADER), | |
("value", KSCAMERA_EXTENDEDPROP_VALUE), | |
] | |
# Helper methods for serialization and deserialization | |
def to_bytes(item): | |
return bytearray(item) | |
def get_extended_camera_control_payload(controller, control_id): | |
prop = KSPROPERTY( | |
KSPROPERTYSETID_ExtendedCameraControl.bytes_le, | |
control_id, | |
KsPropertyKind.KSPROPERTY_TYPE_GET.value, | |
) | |
serialized_prop = to_bytes(prop) | |
return controller.get_device_property_by_extended_id(serialized_prop, None) | |
def set_extended_camera_control_payload(controller, control_id, flag): | |
prop = KSPROPERTY( | |
KSPROPERTYSETID_ExtendedCameraControl.bytes_le, | |
control_id, | |
KsPropertyKind.KSPROPERTY_TYPE_SET.value, | |
) | |
serialized_prop = to_bytes(prop) | |
payload = KsBasicCameraExtendedPropPayload() | |
payload.header.Flags = flag | |
payload.header.Capability = 0 | |
payload.header.Size = sizeof(KsBasicCameraExtendedPropPayload) | |
payload.header.Version = 1 | |
payload.header.PinId = KSCAMERA_EXTENDEDPROP_FILTERSCOPE | |
payload.value.Value = 0 | |
serialized_payload = to_bytes(payload) | |
return controller.set_device_property_by_extended_id( | |
serialized_prop, serialized_payload | |
) | |
async def get_device_supporting_windows_studio(): | |
devices = await DeviceInformation.find_all_async( | |
MediaDevice.get_video_capture_selector(), | |
[DEVPKEY_DeviceInterface_IsWindowsCameraEffectAvailable], | |
) | |
selected_device = next( | |
( | |
device | |
for device in devices | |
if device.enclosure_location.panel == Panel.FRONT | |
), | |
None, | |
) | |
source_groups = await MediaFrameSourceGroup.find_all_async() | |
selected_source_group = next( | |
( | |
source_group | |
for source_group in source_groups | |
if source_group.id == selected_device.id | |
), | |
None, | |
) | |
return selected_device, selected_source_group | |
async def get_media_control(device_id, source_group): | |
capture_settings = MediaCaptureInitializationSettings() | |
capture_settings.source_group = source_group | |
capture_settings.video_device_id = device_id | |
capture_settings.memory_preference = MediaCaptureMemoryPreference.CPU | |
capture_settings.streaming_capture_mode = StreamingCaptureMode.VIDEO | |
capture_settings.sharing_mode = MediaCaptureSharingMode.EXCLUSIVE_CONTROL | |
media_capture = MediaCapture() | |
await media_capture.initialize_async(capture_settings) | |
return media_capture | |
async def main(): | |
( | |
selected_device, | |
selected_source_group, | |
) = await get_device_supporting_windows_studio() | |
print(f"Device id selected: {selected_device.id}") | |
media_control = await get_media_control(selected_device.id, selected_source_group) | |
print(f"Media control selected: {media_control}") | |
payload = get_extended_camera_control_payload( | |
media_control.video_device_controller, 40 | |
) | |
x = wf.IPropertyValue._from(payload.value) | |
o = KsBasicCameraExtendedPropPayload.from_buffer(x.get_uint8_array()) | |
current_eye_gaze_status = "Enabled" if o.header.Flags == 1 else "Disabled" | |
print(f"Current eye gaze status: {current_eye_gaze_status}") | |
payload = get_extended_camera_control = get_extended_camera_control_payload( | |
media_control.video_device_controller, 41 | |
) | |
x = wf.IPropertyValue._from(payload.value) | |
o = KsBasicCameraExtendedPropPayload.from_buffer(x.get_uint8_array()) | |
current_background_blur_status = "Enabled" if o.header.Flags == 1 else "Disabled" | |
print(f"Current background blur status: {current_background_blur_status}") | |
payload = get_extended_camera_control_payload( | |
media_control.video_device_controller, 43 | |
) | |
x = wf.IPropertyValue._from(payload.value) | |
o = KsBasicCameraExtendedPropPayload.from_buffer(x.get_uint8_array()) | |
current_automatic_framming_status = "Enabled" if o.header.Flags == 1 else "Disabled" | |
print(f"Current automatic framing status: {current_automatic_framming_status}") | |
input("Press enter to enable standard background blur...") | |
result = set_extended_camera_control_payload( | |
media_control.video_device_controller, 41, 1 | |
) | |
input("Press enter to enable portrait background blur...") | |
result = set_extended_camera_control_payload( | |
media_control.video_device_controller, 41, 0 | |
) | |
input("Press enter to disable background blur...") | |
result = set_extended_camera_control_payload( | |
media_control.video_device_controller, 41, 0 | |
) | |
input("Press enter to enable automatic framing...") | |
result = set_extended_camera_control_payload( | |
media_control.video_device_controller, 43, 1 | |
) | |
input("Press enter to disable automatic framing...") | |
result = set_extended_camera_control_payload( | |
media_control.video_device_controller, 43, 0 | |
) | |
if __name__ == "__main__": | |
asyncio.run(main()) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import uuid | |
from ctypes import * | |
from enum import Enum | |
# GUID equivalent | |
KSPROPERTYSETID_ExtendedCameraControl = uuid.UUID("1CB79112-C0D2-4213-9CA6-CD4FDB927972") | |
KSCAMERA_EXTENDEDPROP_FILTERSCOPE = 0xFFFFFFFF | |
# Enums | |
class ExtendedControlKind(Enum): | |
KSPROPERTY_CAMERACONTROL_EXTENDED_EYEGAZECORRECTION = 40 | |
KSPROPERTY_CAMERACONTROL_EXTENDED_BACKGROUNDSEGMENTATION = 41 | |
KSPROPERTY_CAMERACONTROL_EXTENDED_DIGITALWINDOW = 43 | |
class BackgroundSegmentationCapabilityKind(Enum): | |
KSCAMERA_EXTENDEDPROP_BACKGROUNDSEGMENTATION_OFF = 0 | |
KSCAMERA_EXTENDEDPROP_BACKGROUNDSEGMENTATION_BLUR = 1 | |
KSCAMERA_EXTENDEDPROP_BACKGROUNDSEGMENTATION_MASK = 2 | |
KSCAMERA_EXTENDEDPROP_BACKGROUNDSEGMENTATION_SHALLOWFOCUS = 4 | |
class EyeGazeCorrectionCapabilityKind(Enum): | |
KSCAMERA_EXTENDEDPROP_EYEGAZECORRECTION_OFF = 0 | |
KSCAMERA_EXTENDEDPROP_EYEGAZECORRECTION_ON = 1 | |
KSCAMERA_EXTENDEDPROP_EYEGAZECORRECTION_STARE = 2 | |
class KsPropertyKind(Enum): | |
KSPROPERTY_TYPE_GET = 0x00000001 | |
KSPROPERTY_TYPE_SET = 0x00000002 | |
KSPROPERTY_TYPE_TOPOLOGY = 0x10000000 | |
# Structures | |
class KsProperty(Structure): | |
_fields_ = [("Set", c_ubyte * 16), | |
("Id", c_uint32), | |
("Flags", c_uint32)] | |
def __init__(self, set, id=0, flags=0): | |
super().__init__() | |
self.Set = set | |
self.Id = id | |
self.Flags = flags | |
class KSCAMERA_EXTENDEDPROP_HEADER(Structure): | |
_fields_ = [("Version", c_uint32), | |
("PinId", c_uint32), | |
("Size", c_uint32), | |
("Result", c_uint32), | |
("Flags", c_uint64), | |
("Capability", c_uint64)] | |
class KSCAMERA_EXTENDEDPROP_VALUE(Structure): | |
_fields_ = [("Value", c_uint64)] | |
class KsBasicCameraExtendedPropPayload(Structure): | |
_fields_ = [("header", KSCAMERA_EXTENDEDPROP_HEADER), | |
("value", KSCAMERA_EXTENDEDPROP_VALUE)] | |
class KSCAMERA_EXTENDEDPROP_BACKGROUNDSEGMENTATION_CONFIGCAPS(Structure): | |
_fields_ = [("MaskResolutionX", c_int32), | |
("MaskResolutionY", c_int32), | |
("MaxFrameRateNumerator", c_int32), | |
("MaxFrameRateDenominator", c_int32), | |
("ResolutionX", c_int32), | |
("ResolutionY", c_int32), | |
("SubType", c_ubyte * 16)] | |
class KsBackgroundCameraExtendedPropPayload(Structure): | |
_fields_ = [("header", KSCAMERA_EXTENDEDPROP_HEADER), | |
("value", POINTER(KSCAMERA_EXTENDEDPROP_BACKGROUNDSEGMENTATION_CONFIGCAPS))] | |
# Helper methods for serialization and deserialization | |
def to_bytes(item): | |
return bytearray(item) | |
def from_bytes(bytes, start_index=0): | |
return bytes[start_index:] | |
def set_extended_control_flags(controller, control_kind, flags): | |
# Create a KsProperty for the specified extended control | |
ks_prop = KsProperty( | |
KSPROPERTYSETID_ExtendedCameraControl.bytes, | |
control_kind.value, | |
KsProperty.KsPropertyKind.KSPROPERTY_TYPE_SET.value | |
) | |
byte_ks_prop = to_bytes(ks_prop) | |
# Create a payload for the specified extended control that includes the specified flags value | |
payload = KsBasicCameraExtendedPropPayload() | |
payload.header.Flags = flags | |
payload.header.Capability = 0 | |
payload.header.Size = sizeof(KsBasicCameraExtendedPropPayload) | |
payload.header.Version = 1 | |
payload.header.PinId = KSCAMERA_EXTENDEDPROP_FILTERSCOPE | |
payload.value.Value = 0 | |
byte_payload = to_bytes(payload) | |
# Send a SET command with the KsProperty and payload and retrieve the result status | |
result_status = controller.set_device_property_by_extended_id(byte_ks_prop, byte_payload) | |
if result_status != "Success": # Assuming "Success" is a possible return value | |
raise Exception(f"Unexpectedly could not SET flags={flags} for control={control_kind}, status={result_status}") | |
def get_extended_control_payload(controller, control_kind): | |
set_bytes = (c_ubyte * 16).from_buffer_copy(KSPROPERTYSETID_ExtendedCameraControl.bytes) | |
# Create a KsProperty for the specified extended control | |
ks_prop = KsProperty( | |
set_bytes, | |
control_kind.value, | |
KsPropertyKind.KSPROPERTY_TYPE_GET.value | |
) | |
byte_payload = to_bytes(ks_prop) | |
# Send a GET command with the KsProperty and retrieve the result | |
result_payload = controller.get_device_property_by_extended_id(byte_payload, None) | |
if result_payload is None: | |
raise Exception(f"Unexpectedly could not GET payload for control={control_kind}, null payload") | |
# Assuming result_payload has a 'Status' attribute and a 'Value' attribute | |
breakpoint() | |
if result_payload.status != "Success": # Assuming "Success" is a possible return value | |
raise Exception(f"Unexpectedly could not GET payload for control={control_kind}, status={result_payload.Status}") | |
return result_payload.Value |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from winsdk.windows.devices.enumeration import DeviceInformation | |
from winsdk.windows.media.devices import MediaDevice | |
from winsdk.windows.devices.enumeration import Panel | |
from winsdk.windows.media.capture import MediaCaptureMemoryPreference | |
from winsdk.windows.media.capture import StreamingCaptureMode | |
from winsdk.windows.media.capture import MediaCaptureSharingMode | |
from winsdk.windows.media.capture import MediaCaptureInitializationSettings | |
from winsdk.windows.media.capture import MediaCapture, MediaStreamType, MediaCaptureDeviceExclusiveControlReleaseMode | |
from winsdk.windows.media.capture.frames import MediaFrameSourceKind, MediaFrameSourceGroup | |
import winsdk.windows.foundation as wf | |
from ks_helper import KsBasicCameraExtendedPropPayload, to_bytes, KSCAMERA_EXTENDEDPROP_HEADER, KSCAMERA_EXTENDEDPROP_FILTERSCOPE, KSCAMERA_EXTENDEDPROP_VALUE | |
import asyncio | |
import uuid | |
from ctypes import c_uint32 | |
import ctypes | |
from ctypes import wintypes | |
KSPROPERTYSETID_ExtendedCameraControl = uuid.UUID('1CB79112-C0D2-4213-9CA6-CD4FDB927972') | |
DEVPKEY_DeviceInterface_IsWindowsCameraEffectAvailable = "{6EDC630D-C2E3-43B7-B2D1-20525A1AF120} 4" | |
# Define the KSPROPERTY structure | |
class KSPROPERTY(ctypes.Structure): | |
_fields_ = [ | |
('Set', ctypes.c_char * 16), # GUID is represented as a 16-byte array | |
('Id', ctypes.c_ulong), | |
('Flags', ctypes.c_ulong) | |
] | |
def get_extended_camera_control_payload(controller, control_id): | |
# Initialize the KSPROPERTY structure | |
prop = KSPROPERTY() | |
prop.Set = KSPROPERTYSETID_ExtendedCameraControl.bytes_le | |
prop.Id = control_id | |
prop.Flags = 0x00000001 # KSPROPERTY_TYPE_GET | |
# Serialize to byte buffer | |
serialized_prop = ctypes.string_at(ctypes.byref(prop), ctypes.sizeof(prop)) | |
# Send the GET command | |
# Note: This part is highly dependent on the specific COM object and method you're using. | |
# The following is a placeholder for the actual call. | |
get_result = controller.get_device_property_by_extended_id(serialized_prop, None) | |
return get_result | |
def create_camera_payload(flags): | |
# Initialize the KSCAMERA_EXTENDEDPROP_HEADER structure | |
header = KSCAMERA_EXTENDEDPROP_HEADER() | |
header.Flags = flags | |
header.Capability = 0 | |
header.Size = ctypes.sizeof(KsBasicCameraExtendedPropPayload) | |
header.Version = 1 | |
header.PinId = KSCAMERA_EXTENDEDPROP_FILTERSCOPE | |
# Initialize the KSCAMERA_EXTENDEDPROP_VALUE structure | |
value = KSCAMERA_EXTENDEDPROP_VALUE() | |
value.Value = 0 | |
# Combine the header and value into a single payload | |
payload = KsBasicCameraExtendedPropPayload() | |
payload.header = header | |
payload.value = value | |
return payload | |
def set_extended_camera_control_payload(controller, control_id, flag): | |
# Initialize the KSPROPERTY structure | |
prop = KSPROPERTY() | |
prop.Set = KSPROPERTYSETID_ExtendedCameraControl.bytes_le | |
prop.Id = control_id | |
prop.Flags = 0x00000002 # KSPROPERTY_TYPE_SET | |
# Serialize to byte buffer | |
serialized_prop = ctypes.string_at(ctypes.byref(prop), ctypes.sizeof(prop)) | |
payload = create_camera_payload(flag) | |
serialized_payload = ctypes.string_at(ctypes.byref(payload), ctypes.sizeof(payload)) | |
# Send the SET command | |
# Note: This part is highly dependent on the specific COM object and method you're using. | |
# The following is a placeholder for the actual call. | |
get_result = controller.set_device_property_by_extended_id(serialized_prop, serialized_payload) | |
return get_result | |
async def get_device_supporting_windows_studio(): | |
devices = await DeviceInformation.find_all_async(MediaDevice.get_video_capture_selector(), [DEVPKEY_DeviceInterface_IsWindowsCameraEffectAvailable]) | |
selected_device = next((device for device in devices if device.enclosure_location.panel == Panel.FRONT), None) | |
source_groups = await MediaFrameSourceGroup.find_all_async() | |
selected_source_group = next((source_group for source_group in source_groups if source_group.id == selected_device.id), None) | |
return selected_device, selected_source_group | |
async def get_media_control(device_id, source_group): | |
capture_settings = MediaCaptureInitializationSettings() | |
capture_settings.source_group = source_group | |
capture_settings.video_device_id = device_id | |
capture_settings.memory_preference = MediaCaptureMemoryPreference.CPU | |
capture_settings.streaming_capture_mode = StreamingCaptureMode.VIDEO | |
capture_settings.sharing_mode = MediaCaptureSharingMode.EXCLUSIVE_CONTROL | |
media_capture = MediaCapture() | |
await media_capture.initialize_async(capture_settings) | |
return media_capture | |
async def main(): | |
selected_device, selected_source_group = await get_device_supporting_windows_studio() | |
print(f"Device id selected: {selected_device.id}") | |
media_control = await get_media_control(selected_device.id, selected_source_group) | |
print(f"Media control selected: {media_control}") | |
payload = get_extended_camera_control_payload(media_control.video_device_controller, 40) | |
x = wf.IPropertyValue._from(payload.value) | |
o = KsBasicCameraExtendedPropPayload.from_buffer(x.get_uint8_array()) | |
current_eye_gaze_status = "Enabled" if o.header.Flags == 1 else "Disabled" | |
print(f"Current eye gaze status: {current_eye_gaze_status}") | |
payload = get_extended_camera_control_payload(media_control.video_device_controller, 41) | |
x = wf.IPropertyValue._from(payload.value) | |
o = KsBasicCameraExtendedPropPayload.from_buffer(x.get_uint8_array()) | |
current_background_blur_status = "Enabled" if o.header.Flags == 1 else "Disabled" | |
print(f"Current background blur status: {current_background_blur_status}") | |
payload = get_extended_camera_control_payload(media_control.video_device_controller, 43) | |
x = wf.IPropertyValue._from(payload.value) | |
o = KsBasicCameraExtendedPropPayload.from_buffer(x.get_uint8_array()) | |
current_automatic_framming_status = "Enabled" if o.header.Flags == 1 else "Disabled" | |
print(f"Current automatic framing status: {current_automatic_framming_status}") | |
control_acquired = media_control.video_device_controller.try_acquire_exclusive_control(selected_device.id, MediaCaptureDeviceExclusiveControlReleaseMode.ON_DISPOSE) | |
print(f"{control_acquired = }") | |
input("Press enter to enable background blur...") | |
result = set_extended_camera_control_payload(media_control.video_device_controller, 41, 1) | |
print(result) | |
# input("Press enter to disable background blur...") | |
# result = set_extended_camera_control_payload(media_control.video_device_controller, 41, 0) | |
# print(result) | |
if __name__ == "__main__": | |
asyncio.run(main()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment