Pure-ctypes Python wrapper for the Canon EDSDK (macOS / Linux / Windows)
Project description
python-edsdk-ctypes
Pure-ctypes Python wrapper for the Canon EDSDK (EOS Digital Software Development Kit).
- Cross-platform: macOS, Linux, Windows — single codebase, no C++ extension to compile.
- Python 3.12+.
- No build step: ships as a normal pure-Python package.
- Designed for use cases like live-view streaming, QR / fiducial detection, remote shooting, exposure control, and multi-camera setups.
Status: alpha. Public API is subject to change until 0.1.0.
Distribution: PyPI as
edsdk-ctypes—import edsdk.
Why another EDSDK wrapper?
The existing edsdk-python is a CPython C++ extension and works on Windows only. This project takes a different approach:
edsdk-python |
edsdk-ctypes (this) |
|
|---|---|---|
| Implementation | C++ extension (Python.h) |
Pure Python via ctypes |
| Platforms | Windows | macOS, Linux, Windows |
| Install | Requires compiler + EDSDK headers/libs at build time | pip install; EDSDK loaded at runtime |
| Python ABI | Per-version wheel | Any 3.12+ |
The Python-side IntEnum constants under src/edsdk/constants/_generated.py are produced mechanically from the EDSDK header by tools/gen_constants.py. Re-run that script when upgrading to a newer EDSDK release.
EDSDK is NOT included
Canon distributes the EDSDK under NDA. You must obtain it yourself via the Canon Developer Program for your region:
This package only ships Python bindings. No EDSDK headers, libraries, or binaries are redistributed.
Installation
pip install edsdk-ctypes
Then place the EDSDK library where the loader can find it. The loader searches in this order:
EDSDK_LIBRARY_PATHenvironment variable (path to the shared library file or its containing directory)- Standard system search paths (
LD_LIBRARY_PATHon Linux,DYLD_LIBRARY_PATHon macOS,PATHon Windows)
Per-platform expectations:
| OS | File | Typical placement |
|---|---|---|
| Linux | libEDSDK.so (+ siblings under Library/<arch>/) |
EDSDK_LIBRARY_PATH=/opt/edsdk/Library/x86_64 |
| macOS | EDSDK.framework/EDSDK |
EDSDK_LIBRARY_PATH=/Library/Frameworks/EDSDK.framework/EDSDK |
| Windows | EDSDK.dll |
place next to your script, or set EDSDK_LIBRARY_PATH |
Quick start
import edsdk
from edsdk import PropID, SaveTo
with edsdk.SDK():
cameras = edsdk.list_cameras()
print(f"Found {len(cameras)} camera(s)")
for cam in cameras:
info = cam.device_info()
print(f" {info.device_description} via {info.port_name}")
with cam:
print(" ISO:", cam.get_property(PropID.ISOSpeed))
cam.set_property(PropID.SaveTo, int(SaveTo.Host))
cam.set_capacity()
Live view + capture
with edsdk.SDK():
cam = edsdk.list_cameras()[0]
with cam, edsdk.LiveView(cam) as lv:
jpeg_bytes = lv.frame() # returns a JPEG frame
def on_object(event, handle):
if event == edsdk.ObjectEvent.DirItemRequestTransfer:
edsdk.Camera.download_to_file(handle, "captures/")
edsdk.Camera.release_handle(handle)
with edsdk.SDK():
cam = edsdk.list_cameras()[0]
with cam:
cam.on_object_event(on_object)
cam.set_property(edsdk.PropID.SaveTo, int(edsdk.SaveTo.Host))
cam.set_capacity()
cam.take_picture()
See examples/ for full runnable samples:
| Script | What it shows |
|---|---|
list_cameras.py |
Enumerate connected cameras |
show_properties.py |
Read common properties + ISO descriptor |
take_picture.py |
Single-shot capture with host-side download |
liveview_save.py |
Stream live view to N JPEG files |
liveview_qrcode.py |
Live view → OpenCV → QR detection (positions) |
multi_camera.py |
Open multiple cameras and grab one frame each |
Notes for R-series bodies (R / RP / R3 / R5 / R6 / R8 / ...)
Verified against an EOS R3 (firmware 1.7.1), some quirks worth knowing:
- Capture requires live view to be active. Without
Evf_OutputDevicehaving the PC bit set,PressShutterButtonis rejected withEDS_ERR_PARTIAL_DELETION. The high-levelCamera.take_picturedrivesUILock → PressShutter → UIUnLock, but you must wrap the call in a :class:LiveViewcontext (seeexamples/take_picture.py). - The legacy
CameraCommand.TakePictureopcode is not accepted — R-bodies requirePressShutterButton.Camera.take_picture()uses the modern flow. - An SD card must be inserted even with
SaveTo.Host— the body stages the file on card before transfer. Without a card, capture fails withPARTIAL_DELETION. UILockmust useparam=0.param=1(TFT off) drops the USB connection on R3 (subsequent calls returnDEVICE_NOT_FOUND).- Property changes need a small settle delay (~500 ms) before the
next
PressShutterButton. Without it, R3 returnsPARTIAL_DELETION. - All EDSDK calls must run on the same thread. Don't pump events
from a worker thread while the main thread issues commands — that
corrupts SDK state. Use
edsdk.pump_events(...)/edsdk.wait_until(...)from the thread that owns the SDK.
Roadmap
- Library loader (Linux / macOS / Windows) with
EDSDK_LIBRARY_PATHoverride -
EdsInitializeSDK/EdsTerminateSDK/EdsGetEvent+pump_events/wait_until(single-thread) - Camera enumeration,
OpenSession/CloseSession,GetDeviceInfo - Property get/set with type-aware marshalling (incl. Time, Rational, Rect, FocusInfo, PictureStyleDesc)
- Object / Property / State / CameraAdded event handlers
-
TakePicture+ download flow (Camera.download_to_file) - Live-view (
EvfImageRef) →bytes - High-level
Cameraclass encapsulating the above - Numpy / OpenCV zero-copy decoding helper
- Property descriptor → human-readable shutter / aperture / ISO mapping
- Windows COM message-pump helper (parallel to
pump_events) - Smoke-test suite that runs against an attached body in CI
Regenerating constants for a new SDK
src/edsdk/constants/_generated.py is produced mechanically from the
EDSDK header by tools/gen_constants.py. Re-run it whenever you bump to
a newer SDK release.
pipenv run python tools/gen_constants.py \
/path/to/EDSDK/Header/EDSDKTypes.h \
-o src/edsdk/constants/_generated.py
git diff src/edsdk/constants/_generated.py # review additions / removals
pipenv run pytest -q # confirm nothing broke
That single output file is everything that ships — edsdk.constants
re-exports its IntEnum classes automatically, so the public API
(edsdk.PropID, edsdk.CameraCommand, …) updates with no further edits.
How the conversion works
gen_constants.py reads EDSDKTypes.h directly (no preprocessor) and
recognises two header conventions:
| Header form | Example | Generated Python |
|---|---|---|
Macro group — many #defines sharing a kEds<Group>_ prefix |
#define kEdsPropID_ProductName 0x00000002 |
class PropID(IntEnum): ProductName = 0x00000002 |
| Typedef enum | typedef enum { kEdsSaveTo_Camera = 1, kEdsSaveTo_Host = 2, kEdsSaveTo_Both = kEdsSaveTo_Camera | kEdsSaveTo_Host } EdsSaveTo; |
class SaveTo(IntEnum): Camera = 0x1; Host = 0x2; Both = 0x3 |
Naming rules:
- Macro groups are emitted under the bare group name (
PropID,CameraCommand,CameraStatusCommand,ObjectEvent,PropertyEvent,StateEvent). Member names keep the suffix after the prefix (kEdsPropID_Evf_OutputDevice→PropID.Evf_OutputDevice). - Typedef enums drop the leading
Edsfrom the type name (EdsSaveTo→SaveTo,EdsAccess→Access). The longest common prefix shared by all members is stripped to leave clean names (kEdsAccess_Read→Access.Read). - Members starting with a digit are prefixed with
_; members that collide with Python keywords get a trailing_. Duplicate names (occasionally produced by header aliasing) are de-duplicated, keeping the first occurrence.
Constant-expression support during parsing:
- Integer literals in decimal or hex, optionally with the
Lsuffix. - References to earlier members in the same enum
(
Both = Camera | Host). - Bitwise operators (
|,&,^,<<,>>), arithmetic (+,-,*,/), and parentheses. Anything outside this safelist (string literals, function calls, etc.) is rejected with a clear error. - Members without an explicit
= valuecontinue from the previous member +1 — matching Cenumsemantics.
Things the generator does NOT cover
- Av / Tv / ISO speed value tables — these aren't in the header.
They live in
Document/EDSDK_API_*.pdf. UseCamera.get_property_desc(PropID.ISOSpeed)on a real body to enumerate the values supported by that camera. - Error code names — handled separately in
src/edsdk/errors.py(parsed fromEDSDKErrors.hmentally, not from a generator). - Struct layouts — defined by hand in
src/edsdk/_types.pysince they need ctypes-specific scalar choices.
License
MIT — see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file edsdk_ctypes-0.0.1.tar.gz.
File metadata
- Download URL: edsdk_ctypes-0.0.1.tar.gz
- Upload date:
- Size: 16.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ff14ea2d24ab21bcb5e5bcf6a273582b0ce91f6f43b6c7326931e6eea9bffee3
|
|
| MD5 |
79f53b508536d0f99e6dc07368fc153a
|
|
| BLAKE2b-256 |
ba1f0cc1815042e1e743947e4375830d76328121e3e11827e7cf97348c890c49
|
Provenance
The following attestation bundles were made for edsdk_ctypes-0.0.1.tar.gz:
Publisher:
publish.yaml on chibiegg/python-edsdk-ctypes
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
edsdk_ctypes-0.0.1.tar.gz -
Subject digest:
ff14ea2d24ab21bcb5e5bcf6a273582b0ce91f6f43b6c7326931e6eea9bffee3 - Sigstore transparency entry: 1429082342
- Sigstore integration time:
-
Permalink:
chibiegg/python-edsdk-ctypes@6c77bf5149a420872ddbce3c81ce7ec5c9d756de -
Branch / Tag:
refs/tags/0.0.1 - Owner: https://github.com/chibiegg
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yaml@6c77bf5149a420872ddbce3c81ce7ec5c9d756de -
Trigger Event:
release
-
Statement type:
File details
Details for the file edsdk_ctypes-0.0.1-py3-none-any.whl.
File metadata
- Download URL: edsdk_ctypes-0.0.1-py3-none-any.whl
- Upload date:
- Size: 7.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2615cee00706f70ccde18f7fab9a50cfd288b2ac9288e91e10a338b5f8ee70b3
|
|
| MD5 |
66eca37e61bd87d9c2af1ff1fa5f3a06
|
|
| BLAKE2b-256 |
de11e746855dae0c5474cd946f8b14f71d00c1d5901be8a60a1f52775d6eb13d
|
Provenance
The following attestation bundles were made for edsdk_ctypes-0.0.1-py3-none-any.whl:
Publisher:
publish.yaml on chibiegg/python-edsdk-ctypes
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
edsdk_ctypes-0.0.1-py3-none-any.whl -
Subject digest:
2615cee00706f70ccde18f7fab9a50cfd288b2ac9288e91e10a338b5f8ee70b3 - Sigstore transparency entry: 1429082347
- Sigstore integration time:
-
Permalink:
chibiegg/python-edsdk-ctypes@6c77bf5149a420872ddbce3c81ce7ec5c9d756de -
Branch / Tag:
refs/tags/0.0.1 - Owner: https://github.com/chibiegg
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yaml@6c77bf5149a420872ddbce3c81ce7ec5c9d756de -
Trigger Event:
release
-
Statement type: