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.
Metadata
Release files for edsdk-ctypes 0.0.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| edsdk_ctypes-0.0.1.tar.gz | 16.5 kB | With provenance | Details for edsdk_ctypes-0.0.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| edsdk_ctypes-0.0.1-py3-none-any.whl | Python 3 | none | any | Details for edsdk_ctypes-0.0.1-py3-none-any.whl |
Total release size: 23.4 kB
Release files / edsdk_ctypes-0.0.1.tar.gz
| Download URL | edsdk_ctypes-0.0.1.tar.gz |
|---|---|
| Size | 16.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ff14ea2d24ab21bcb5e5bcf6a273582b0ce91f6f43b6c7326931e6eea9bffee3
|
|
BLAKE2b-256 checksum How to use checksums |
ba1f0cc1815042e1e743947e4375830d76328121e3e11827e7cf97348c890c49
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.13
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 2, 2026.
Transparency logRelease files / edsdk_ctypes-0.0.1-py3-none-any.whl
| Download URL | edsdk_ctypes-0.0.1-py3-none-any.whl |
|---|---|
| Size | 7.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2615cee00706f70ccde18f7fab9a50cfd288b2ac9288e91e10a338b5f8ee70b3
|
|
BLAKE2b-256 checksum How to use checksums |
de11e746855dae0c5474cd946f8b14f71d00c1d5901be8a60a1f52775d6eb13d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.13
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 2, 2026.
Transparency logSecurity for edsdk-ctypes 0.0.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for edsdk_ctypes-0.0.1.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 2, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.