pyedsdk 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,31 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 François MARGALL
4
+
5
+ This project is an independent Python binding for Canon EDSDK.
6
+ Canon EDSDK is proprietary software owned by Canon Inc. and is
7
+ not distributed with this project.
8
+ This project is not affiliated with, endorsed by, or sponsored
9
+ by Canon Inc.
10
+
11
+ This license applies only to the Python source code of PyEDSDK.
12
+ Users must obtain and comply with Canon’s SDK License Agreement
13
+ separately.
14
+
15
+ Permission is hereby granted, free of charge, to any person obtaining a copy
16
+ of this software and associated documentation files (the "Software"), to deal
17
+ in the Software without restriction, including without limitation the rights
18
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
19
+ copies of the Software, and to permit persons to whom the Software is
20
+ furnished to do so, subject to the following conditions:
21
+
22
+ The above copyright notice and this permission notice shall be included in all
23
+ copies or substantial portions of the Software.
24
+
25
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
26
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
27
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
28
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
29
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
30
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
31
+ SOFTWARE.
pyedsdk-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,9 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyedsdk
3
+ Version: 0.1.0
4
+ License-File: LICENSE.txt
5
+ Requires-Dist: pywin32>=306; platform_system == "Windows"
6
+ Provides-Extra: dev
7
+ Requires-Dist: pytest; extra == "dev"
8
+ Requires-Dist: pytest-timeout; extra == "dev"
9
+ Dynamic: license-file
@@ -0,0 +1,62 @@
1
+ ## Installation
2
+
3
+ > [!WARNING]
4
+ > - `pyedsdk` currently only supports Windows.
5
+ > - You must download and install the Canon EDSDK separatly from Canon directly.
6
+ > - For intellectual property and licensing reasons, the EDSDK dynamic libraries (`EDSDK.dll`) are not distributed with this Python package.
7
+
8
+ Canon requires developers to request access to the SDK directly from them.
9
+ Please visit the official [Canon Developer website](https://www.canon.fr/business/imaging-solutions/sdk/) and obtain the EDSDK before using this module. This may take a few days before you can have access.
10
+
11
+ ### 1. Install Canon EDSDK
12
+
13
+ After downloading the SDK from Canon:
14
+ 1. Install or extract the SDK on your machine
15
+ 1. Locate the `EDSDK.dll` associated to your architecture (for Windows, either inside `EDSDK` or `EDSDK_64` folder). You can choose to paste it for instance in a new folder located at `C:\Canon\EDSDK\Dll\EDSDK.dll`.
16
+
17
+ ### 2. Make the DLL discoverable
18
+
19
+ #### (Recommended) Set environment variable
20
+
21
+ Simply run in your terminal the following command with the path to the DLL:
22
+
23
+ ```bash
24
+ setx EDSDK_PATH "C:\Canon\EDSDK\Dll\EDSDK.dll"
25
+ ```
26
+ And restart your terminal afterwards.
27
+
28
+ #### (Alternative) Add SDK folder to your PATH
29
+
30
+ Add the directory containing `EDSDK.dll` to your Windows `PATH` environment variable.
31
+
32
+ #### (Alternative) Pass the path explicitly in Python
33
+
34
+ Simply jump to the next part for the installation of the `pyedsdk` package, and when calling it, initialize the DLL with the following code:
35
+
36
+ ```python
37
+ from pyedsdk.core.loader import loadSDKLib
38
+
39
+ loadSDKLib(r"C:\Canon\EDSDK\Dll\EDSDK.dll")
40
+ ```
41
+
42
+ ### Installing `pyedsdk`
43
+
44
+ #### (Recommended) Using `pip`
45
+
46
+ The safest and easiest way to install the latest version is via PyPI:
47
+
48
+ ```bash
49
+ pip install pyedsdk
50
+ ```
51
+
52
+ ## License
53
+
54
+ This package is an independent, unofficial Python binding for Canon EDSDK. Canon EDSDK is proprietary software owned by Canon Inc. This project:
55
+ - does **not** redistribute Canon binaries
56
+ - does **not** modify Canon SDK files
57
+ - **requires** users to agree to Canon's license separately
58
+ - is **not affiliated with, endorsed by, or sponsored by Canon Inc.**
59
+
60
+ Canon EDSDK remains the exclusive property of Canon Inc. Users are responsible for complying with Canon’s SDK License Agreement.
61
+
62
+ PyEDSDK is distributed under the MIT License. See `License.txt` for more information.
@@ -0,0 +1,46 @@
1
+ import ctypes
2
+ import time
3
+
4
+ from ._errors import _ErrorCode
5
+ from ._types import _BaseRef
6
+ from ._types import _ObjectEvent
7
+
8
+
9
+ _ObjectEventHandler = ctypes.WINFUNCTYPE(
10
+ ctypes.c_uint32, # _ErrorCode
11
+ ctypes.c_uint32, # _ObjectEvent
12
+ _BaseRef,
13
+ ctypes.c_void_p
14
+ )
15
+
16
+ _PM_REMOVE = 0x0001
17
+ _user32 = ctypes.windll.user32
18
+
19
+ class MSG(ctypes.Structure):
20
+ _fields_ = [
21
+ ("hwnd" , ctypes.c_void_p),
22
+ ("message", ctypes.c_uint),
23
+ ("wParam" , ctypes.c_void_p),
24
+ ("lParam" , ctypes.c_void_p),
25
+ ("time" , ctypes.c_uint32),
26
+ ("pt_x" , ctypes.c_long),
27
+ ("pt_y" , ctypes.c_long),
28
+ ]
29
+
30
+ def _pumpWindowsMessages():
31
+ msg = MSG()
32
+
33
+ while _user32.PeekMessageW(
34
+ ctypes.byref(msg), None, 0, 0, _PM_REMOVE):
35
+ _user32.TranslateMessage(ctypes.byref(msg))
36
+ _user32.DispatchMessageW(ctypes.byref(msg))
37
+
38
+ def _waitForEvent(event, timeout=15):
39
+ start = time.time()
40
+
41
+ while not event.is_set():
42
+ _pumpWindowsMessages()
43
+ time.sleep(0.01)
44
+
45
+ if time.time() - start > timeout:
46
+ raise TimeoutError(f"EDSDK event timeout after {timeout} seconds")
@@ -0,0 +1,464 @@
1
+ from enum import IntEnum
2
+
3
+
4
+ class _Aperture(IntEnum):
5
+ _f1 = 0x08
6
+ _f1_1 = 0x0B
7
+ _f1_2 = 0x0C
8
+ _f1_2_p = 0x0D
9
+ _f1_4 = 0x10
10
+ _f1_6 = 0x13
11
+ _f1_8 = 0x14
12
+ _f1_8_p = 0x15
13
+ _f2 = 0x18
14
+ _f2_2 = 0x1B
15
+ _f2_5 = 0x1C
16
+ _f2_5_p = 0x1D
17
+ _f2_8 = 0x20
18
+ _f3_2 = 0x23
19
+ _f3_4 = 0x85
20
+ _f3_5 = 0x24
21
+ _f3_5_p = 0x25
22
+ _f4 = 0x28
23
+ _f4_5 = 0x2B
24
+ _f5 = 0x2D
25
+ _f5_6 = 0x30
26
+ _f6_3 = 0x33
27
+ _f6_7 = 0x34
28
+ _f7_1 = 0x35
29
+ _f8 = 0x38
30
+ _f9 = 0x3B
31
+ _f9_5 = 0x3C
32
+ _f10 = 0x3D
33
+ _f11 = 0x40
34
+ _f13_p = 0x43
35
+ _f13 = 0x44
36
+ _f14 = 0x45
37
+ _f16 = 0x48
38
+ _f18 = 0x4B
39
+ _f19 = 0x4C
40
+ _f20 = 0x4D
41
+ _f22 = 0x50
42
+ _f25 = 0x53
43
+ _f27 = 0x54
44
+ _f29 = 0x55
45
+ _f32 = 0x58
46
+ _f36 = 0x5B
47
+ _f38 = 0x5C
48
+ _f40 = 0x5D
49
+ _f45 = 0x60
50
+ _f51 = 0x63
51
+ _f54 = 0x64
52
+ _f57 = 0x65
53
+ _f64 = 0x68
54
+ _f72 = 0x6B
55
+ _f76 = 0x6C
56
+ _f80 = 0x6D
57
+ _f91 = 0x70
58
+
59
+ _Invalid = 0xFFFFFFFF
60
+
61
+ @property
62
+ def f_number(self) -> float | None:
63
+ if self is self._Invalid:
64
+ return None
65
+
66
+ mapping = {
67
+ self._f1 : 1.0,
68
+ self._f1_1 : 1.1,
69
+ self._f1_2 : 1.2,
70
+ self._f1_2_p: 1.2 * (2 ** (1/6)),
71
+ self._f1_4 : 1.4,
72
+ self._f1_6 : 1.6,
73
+ self._f1_8 : 1.8,
74
+ self._f1_8_p: 1.8 * (2 ** (1/6)),
75
+ self._f2 : 2.0,
76
+ self._f2_2 : 2.2,
77
+ self._f2_5 : 2.5,
78
+ self._f2_5_p: 2.5 * (2 ** (1/6)),
79
+ self._f2_8 : 2.8,
80
+ self._f3_2 : 3.2,
81
+ self._f3_4 : 3.4,
82
+ self._f3_5 : 3.5,
83
+ self._f3_5_p: 3.5 * (2 ** (1/6)),
84
+ self._f4 : 4.0,
85
+ self._f4_5 : 4.5,
86
+ self._f5 : 5.0,
87
+ self._f5_6 : 5.6,
88
+ self._f6_3 : 6.3,
89
+ self._f6_7 : 6.7,
90
+ self._f7_1 : 7.1,
91
+ self._f8 : 8.0,
92
+ self._f9 : 9.0,
93
+ self._f9_5 : 9.5,
94
+ self._f10 : 10.0,
95
+ self._f11 : 11.0,
96
+ self._f13_p : 13.0 * (2 ** (1/6)),
97
+ self._f13 : 13.0,
98
+ self._f14 : 14.0,
99
+ self._f16 : 16.0,
100
+ self._f18 : 18.0,
101
+ self._f19 : 19.0,
102
+ self._f20 : 20.0,
103
+ self._f22 : 22.0,
104
+ self._f25 : 25.0,
105
+ self._f27 : 27.0,
106
+ self._f29 : 29.0,
107
+ self._f32 : 32.0,
108
+ self._f36 : 36.0,
109
+ self._f38 : 38.0,
110
+ self._f40 : 40.0,
111
+ self._f45 : 45.0,
112
+ self._f51 : 51.0,
113
+ self._f54 : 54.0,
114
+ self._f57 : 57.0,
115
+ self._f64 : 64.0,
116
+ self._f72 : 72.0,
117
+ self._f76 : 76.0,
118
+ self._f80 : 80.0,
119
+ self._f91 : 91.0,
120
+ }
121
+ return mapping.get(self, None)
122
+
123
+ @property
124
+ def label(self) -> str:
125
+ if self is self._Invalid:
126
+ return "Not valid"
127
+
128
+ return f"f/{self.f_number}"
129
+
130
+
131
+ class _ShutterSpeed(IntEnum):
132
+ # Long exposures
133
+ _Bulb = 0x0C
134
+ _30s = 0x10
135
+ _25s = 0x13
136
+ _20s = 0x14
137
+ _20s_p = 0x15 # 20" (1/3)
138
+ _15s = 0x18
139
+ _13s = 0x1B
140
+ _10s = 0x1C
141
+ _10s_p = 0x1D # 10" (1/3)
142
+ _8s = 0x20
143
+ _6s = 0x23
144
+ _6s_p = 0x24 # 6" (1/3)
145
+ _5s = 0x25
146
+ _4s = 0x28
147
+ _3s2 = 0x2B
148
+ _3s = 0x2C
149
+ _2s5 = 0x2D
150
+ _2s = 0x30
151
+ _1s6 = 0x33
152
+ _1s5 = 0x34
153
+ _1s3 = 0x35
154
+ _1s = 0x38
155
+ _0s8 = 0x3B
156
+ _0s7 = 0x3C
157
+ _0s6 = 0x3D
158
+ _0s5 = 0x40
159
+ _0s4 = 0x43
160
+ _0s3 = 0x44
161
+ _0s3_p = 0x45
162
+
163
+ # Fractions
164
+ _1_4 = 0x48
165
+ _1_5 = 0x4B
166
+ _1_6 = 0x4C
167
+ _1_6_p = 0x4D
168
+ _1_8 = 0x50
169
+ _1_10_p = 0x53
170
+ _1_10 = 0x54
171
+ _1_13 = 0x55
172
+ _1_15 = 0x58
173
+ _1_20_p = 0x5B
174
+ _1_20 = 0x5C
175
+ _1_25 = 0x5D
176
+ _1_30 = 0x60
177
+ _1_40 = 0x63
178
+ _1_45 = 0x64
179
+ _1_50 = 0x65
180
+ _1_60 = 0x68
181
+ _1_80 = 0x6B
182
+ _1_90 = 0x6C
183
+ _1_100 = 0x6D
184
+ _1_125 = 0x70
185
+ _1_160 = 0x73
186
+ _1_180 = 0x74
187
+ _1_200 = 0x75
188
+ _1_250 = 0x78
189
+ _1_320 = 0x7B
190
+ _1_350 = 0x7C
191
+ _1_400 = 0x7D
192
+ _1_500 = 0x80
193
+ _1_640 = 0x83
194
+ _1_750 = 0x84
195
+ _1_800 = 0x85
196
+ _1_1000 = 0x88
197
+ _1_1250 = 0x8B
198
+ _1_1500 = 0x8C
199
+ _1_1600 = 0x8D
200
+ _1_2000 = 0x90
201
+ _1_2500 = 0x93
202
+ _1_3000 = 0x94
203
+ _1_3200 = 0x95
204
+ _1_4000 = 0x98
205
+ _1_5000 = 0x9B
206
+ _1_6000 = 0x9C
207
+ _1_6400 = 0x9D
208
+ _1_8000 = 0xA0
209
+ _1_10000 = 0xA3
210
+ _1_12800 = 0xA5
211
+ _1_16000 = 0xA8
212
+ _1_20000 = 0xAB
213
+ _1_25600 = 0xAD
214
+ _1_32000 = 0xB0
215
+
216
+ _Invalid = 0xFFFFFFFF
217
+
218
+ @property
219
+ def label(self) -> str:
220
+ mapping = {
221
+ self._Bulb : "Bulb",
222
+ self._30s : '30"',
223
+ self._25s : '25"',
224
+ self._20s : '20"',
225
+ self._20s_p : '20" (1/3)',
226
+ self._15s : '15"',
227
+ self._13s : '13"',
228
+ self._10s : '10"',
229
+ self._10s_p : '10" (1/3)',
230
+ self._8s : '8"',
231
+ self._6s : '6"',
232
+ self._6s_p : '6" (1/3)',
233
+ self._5s : '5"',
234
+ self._4s : '4"',
235
+ self._3s2 : '3"2',
236
+ self._3s : '3"',
237
+ self._2s5 : '2"5',
238
+ self._2s : '2"',
239
+ self._1s6 : '1"6',
240
+ self._1s5 : '1"5',
241
+ self._1s3 : '1"3',
242
+ self._1s : '1"',
243
+ self._0s8 : '0"8',
244
+ self._0s7 : '0"7',
245
+ self._0s6 : '0"6',
246
+ self._0s5 : '0"5',
247
+ self._0s4 : '0"4',
248
+ self._0s3 : '0"3',
249
+ self._0s3_p : '0"3 (1/3)',
250
+ self._1_4 : "1/4",
251
+ self._1_5 : "1/5",
252
+ self._1_6 : "1/6",
253
+ self._1_6_p : "1/6 (1/3)",
254
+ self._1_8 : "1/8",
255
+ self._1_10 : "1/10",
256
+ self._1_10_p : "1/10 (1/3)",
257
+ self._1_13 : "1/13",
258
+ self._1_15 : "1/15",
259
+ self._1_20 : "1/20",
260
+ self._1_20_p : "1/20 (1/3)",
261
+ self._1_25 : "1/25",
262
+ self._1_30 : "1/30",
263
+ self._1_40 : "1/40",
264
+ self._1_45 : "1/45",
265
+ self._1_50 : "1/50",
266
+ self._1_60 : "1/60",
267
+ self._1_80 : "1/80",
268
+ self._1_90 : "1/90",
269
+ self._1_100 : "1/100",
270
+ self._1_125 : "1/125",
271
+ self._1_160 : "1/160",
272
+ self._1_180 : "1/180",
273
+ self._1_200 : "1/200",
274
+ self._1_250 : "1/250",
275
+ self._1_320 : "1/320",
276
+ self._1_350 : "1/350",
277
+ self._1_400 : "1/400",
278
+ self._1_500 : "1/500",
279
+ self._1_640 : "1/640",
280
+ self._1_750 : "1/750",
281
+ self._1_800 : "1/800",
282
+ self._1_1000 : "1/1000",
283
+ self._1_1250 : "1/1250",
284
+ self._1_1500 : "1/1500",
285
+ self._1_1600 : "1/1600",
286
+ self._1_2000 : "1/2000",
287
+ self._1_2500 : "1/2500",
288
+ self._1_3000 : "1/3000",
289
+ self._1_3200 : "1/3200",
290
+ self._1_4000 : "1/4000",
291
+ self._1_5000 : "1/5000",
292
+ self._1_6000 : "1/6000",
293
+ self._1_6400 : "1/6400",
294
+ self._1_8000 : "1/8000",
295
+ self._1_10000: "1/10000",
296
+ self._1_12800: "1/12800",
297
+ self._1_16000: "1/16000",
298
+ self._1_20000: "1/20000",
299
+ self._1_25600: "1/25600",
300
+ self._1_32000: "1/32000",
301
+ self._Invalid: "Not valid",
302
+ }
303
+ return mapping.get(self, "Unknown")
304
+
305
+ @property
306
+ def seconds(self) -> float | None:
307
+ if self in {self._Bulb, self._Invalid}:
308
+ return None
309
+
310
+ mapping = {
311
+ # Long exposures
312
+ self._30s : 30.0,
313
+ self._25s : 25.0,
314
+ self._20s : 20.0,
315
+ self._20s_p: 20.0 * (2 ** (-1/3)),
316
+ self._15s : 15.0,
317
+ self._13s : 13.0,
318
+ self._10s : 10.0,
319
+ self._10s_p: 10.0 * (2 ** (-1/3)),
320
+ self._8s : 8.0,
321
+ self._6s : 6.0,
322
+ self._6s_p : 6.0 * (2 ** (-1/3)),
323
+ self._5s : 5.0,
324
+ self._4s : 4.0,
325
+ self._3s2 : 3.2,
326
+ self._3s : 3.0,
327
+ self._2s5 : 2.5,
328
+ self._2s : 2.0,
329
+ self._1s6 : 1.6,
330
+ self._1s5 : 1.5,
331
+ self._1s3 : 1.3,
332
+ self._1s : 1.0,
333
+ self._0s8 : 0.8,
334
+ self._0s7 : 0.7,
335
+ self._0s6 : 0.6,
336
+ self._0s5 : 0.5,
337
+ self._0s4 : 0.4,
338
+ self._0s3 : 0.3,
339
+ self._0s3_p: 0.3 * (2 ** (-1/3)),
340
+
341
+ # Fractions
342
+ self._1_4 : 1/4,
343
+ self._1_5 : 1/5,
344
+ self._1_6 : 1/6,
345
+ self._1_6_p : (1/6) * (2 ** (-1/3)),
346
+ self._1_8 : 1/8,
347
+ self._1_10 : 1/10,
348
+ self._1_10_p : (1/10) * (2 ** (-1/3)),
349
+ self._1_13 : 1/13,
350
+ self._1_15 : 1/15,
351
+ self._1_20 : 1/20,
352
+ self._1_20_p : (1/20) * (2 ** (-1/3)),
353
+ self._1_25 : 1/25,
354
+ self._1_30 : 1/30,
355
+ self._1_40 : 1/40,
356
+ self._1_45 : 1/45,
357
+ self._1_50 : 1/50,
358
+ self._1_60 : 1/60,
359
+ self._1_80 : 1/80,
360
+ self._1_90 : 1/90,
361
+ self._1_100 : 1/100,
362
+ self._1_125 : 1/125,
363
+ self._1_160 : 1/160,
364
+ self._1_180 : 1/180,
365
+ self._1_200 : 1/200,
366
+ self._1_250 : 1/250,
367
+ self._1_320 : 1/320,
368
+ self._1_350 : 1/350,
369
+ self._1_400 : 1/400,
370
+ self._1_500 : 1/500,
371
+ self._1_640 : 1/640,
372
+ self._1_750 : 1/750,
373
+ self._1_800 : 1/800,
374
+ self._1_1000 : 1/1000,
375
+ self._1_1250 : 1/1250,
376
+ self._1_1500 : 1/1500,
377
+ self._1_1600 : 1/1600,
378
+ self._1_2000 : 1/2000,
379
+ self._1_2500 : 1/2500,
380
+ self._1_3000 : 1/3000,
381
+ self._1_3200 : 1/3200,
382
+ self._1_4000 : 1/4000,
383
+ self._1_5000 : 1/5000,
384
+ self._1_6000 : 1/6000,
385
+ self._1_6400 : 1/6400,
386
+ self._1_8000 : 1/8000,
387
+ self._1_10000: 1/10000,
388
+ self._1_12800: 1/12800,
389
+ self._1_16000: 1/16000,
390
+ self._1_20000: 1/20000,
391
+ self._1_25600: 1/25600,
392
+ self._1_32000: 1/32000,
393
+ }
394
+ return mapping.get(self, None)
395
+
396
+
397
+ class _ISOSpeed(IntEnum):
398
+ _ISO_Auto = 0x00000000
399
+ _ISO_6 = 0x00000028
400
+ _ISO_12 = 0x00000030
401
+ _ISO_25 = 0x00000038
402
+ _ISO_50 = 0x00000040
403
+ _ISO_100 = 0x00000048
404
+ _ISO_125 = 0x0000004B
405
+ _ISO_160 = 0x0000004D
406
+ _ISO_200 = 0x00000050
407
+ _ISO_250 = 0x00000053
408
+ _ISO_320 = 0x00000055
409
+ _ISO_400 = 0x00000058
410
+ _ISO_500 = 0x0000005B
411
+ _ISO_640 = 0x0000005D
412
+ _ISO_800 = 0x00000060
413
+ _ISO_1000 = 0x00000063
414
+ _ISO_1250 = 0x00000065
415
+ _ISO_1600 = 0x00000068
416
+ _ISO_2000 = 0x0000006B
417
+ _ISO_2500 = 0x0000006D
418
+ _ISO_3200 = 0x00000070
419
+ _ISO_4000 = 0x00000073
420
+ _ISO_5000 = 0x00000075
421
+ _ISO_6400 = 0x00000078
422
+ _ISO_8000 = 0x0000007B
423
+ _ISO_10000 = 0x0000007D
424
+ _ISO_12800 = 0x00000080
425
+ _ISO_16000 = 0x00000083
426
+ _ISO_20000 = 0x00000085
427
+ _ISO_25600 = 0x00000088
428
+ _ISO_32000 = 0x0000008B
429
+ _ISO_40000 = 0x0000008D
430
+ _ISO_51200 = 0x00000090
431
+ _ISO_64000 = 0x00000093
432
+ _ISO_80000 = 0x00000095
433
+ _ISO_102400 = 0x00000098
434
+ _ISO_204800 = 0x000000A0
435
+ _ISO_409600 = 0x000000A8
436
+ _ISO_819200 = 0x000000B0
437
+
438
+ @property
439
+ def value(self) -> float:
440
+ if self is _ISOSpeed._ISO_Auto:
441
+ return float("nan")
442
+
443
+ # Enum name is always like "_ISO_6400"
444
+ return float(self.name.split("_")[-1])
445
+
446
+ # Autofocus mode
447
+ class _AFMode(IntEnum):
448
+ _0 = 0
449
+ _1 = 1
450
+ _2 = 2
451
+ _3 = 3
452
+
453
+ _Invalid = 0xFFFFFFFF
454
+
455
+ @property
456
+ def label(self) -> str:
457
+ mapping = {
458
+ self._0: "One-Shot AF",
459
+ self._1: "AI Servo AF",
460
+ self._2: "AI Focus AF",
461
+ self._3: "Manual Focus",
462
+ self._Invalid: "Not valid",
463
+ }
464
+ return mapping.get(self, "Unknown")