capturekit 0.1.0__py3-none-any.whl

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.
Files changed (41) hide show
  1. capturekit/__init__.py +164 -0
  2. capturekit/_backends/__init__.py +0 -0
  3. capturekit/_backends/base.py +89 -0
  4. capturekit/_backends/linux/__init__.py +1 -0
  5. capturekit/_backends/linux/_xlib.py +655 -0
  6. capturekit/_backends/linux/api.py +160 -0
  7. capturekit/_backends/linux/pulse.py +464 -0
  8. capturekit/_backends/linux/x11.py +720 -0
  9. capturekit/_backends/windows/__init__.py +0 -0
  10. capturekit/_backends/windows/_com.py +350 -0
  11. capturekit/_backends/windows/_d3d11.py +307 -0
  12. capturekit/_backends/windows/_win32.py +420 -0
  13. capturekit/_backends/windows/_winrt.py +162 -0
  14. capturekit/_backends/windows/api.py +214 -0
  15. capturekit/_backends/windows/cursor.py +66 -0
  16. capturekit/_backends/windows/dxgi_duplication.py +310 -0
  17. capturekit/_backends/windows/wasapi.py +570 -0
  18. capturekit/_backends/windows/wgc.py +398 -0
  19. capturekit/_pipeline/__init__.py +1 -0
  20. capturekit/_pipeline/aligner.py +177 -0
  21. capturekit/_pipeline/clock.py +47 -0
  22. capturekit/_pipeline/frame_queue.py +120 -0
  23. capturekit/_pipeline/mixer.py +80 -0
  24. capturekit/_pipeline/timeline.py +133 -0
  25. capturekit/_platform.py +24 -0
  26. capturekit/_version.py +1 -0
  27. capturekit/_worker.py +100 -0
  28. capturekit/capabilities.py +79 -0
  29. capturekit/capture.py +141 -0
  30. capturekit/config.py +85 -0
  31. capturekit/encoding/__init__.py +5 -0
  32. capturekit/encoding/ffmpeg.py +356 -0
  33. capturekit/encoding/pipes.py +212 -0
  34. capturekit/errors.py +109 -0
  35. capturekit/py.typed +0 -0
  36. capturekit/recording.py +940 -0
  37. capturekit/sources.py +124 -0
  38. capturekit/types.py +211 -0
  39. capturekit-0.1.0.dist-info/METADATA +112 -0
  40. capturekit-0.1.0.dist-info/RECORD +41 -0
  41. capturekit-0.1.0.dist-info/WHEEL +4 -0
capturekit/sources.py ADDED
@@ -0,0 +1,124 @@
1
+ """Source specifications.
2
+
3
+ Sources are immutable descriptions of *what* to capture. They hold no native
4
+ resources; capture sessions and recorders open them.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import enum
10
+ from dataclasses import dataclass
11
+
12
+ from .types import AudioDevice, Monitor, Rect, Window
13
+
14
+
15
+ class MonitorBackend(enum.Enum):
16
+ AUTO = "auto"
17
+ #: Windows: DXGI Desktop Duplication (default).
18
+ DXGI = "dxgi"
19
+ #: Windows: Windows Graphics Capture of the whole monitor.
20
+ WGC = "wgc"
21
+ #: Linux: X11 GetImage/XShm.
22
+ X11 = "x11"
23
+
24
+
25
+ @dataclass(frozen=True, slots=True)
26
+ class MonitorSource:
27
+ """Capture a monitor, optionally cropped.
28
+
29
+ ``monitor`` may be a :class:`Monitor`, a monitor index, or ``None`` for the
30
+ primary monitor. ``region`` is relative to the monitor's top-left corner.
31
+ """
32
+
33
+ monitor: Monitor | int | None = None
34
+ region: Rect | None = None
35
+ include_cursor: bool = True
36
+ backend: MonitorBackend = MonitorBackend.AUTO
37
+ display: str | None = None
38
+
39
+
40
+ @dataclass(frozen=True, slots=True)
41
+ class WindowSource:
42
+ """Capture one window's own content (never a crop of the desktop).
43
+
44
+ ``window`` may be a :class:`Window` or a native handle. ``region`` is
45
+ relative to the captured content: the whole window (including title bar)
46
+ or, with ``client_area_only=True``, the client area.
47
+
48
+ ``border``: Windows draws a yellow capture border by default. ``False``
49
+ asks the OS to omit it where the OS permits (Windows 11); ``None`` keeps
50
+ the OS default.
51
+ """
52
+
53
+ window: Window | int
54
+ region: Rect | None = None
55
+ client_area_only: bool = False
56
+ include_cursor: bool = False
57
+ border: bool | None = None
58
+ display: str | None = None
59
+
60
+ @property
61
+ def handle(self) -> int:
62
+ return self.window.handle if isinstance(self.window, Window) else int(self.window)
63
+
64
+
65
+ VideoSource = MonitorSource | WindowSource
66
+
67
+
68
+ @dataclass(frozen=True, slots=True)
69
+ class MicrophoneSource:
70
+ """Audio input device (``None`` = system default input)."""
71
+
72
+ device: AudioDevice | str | None = None
73
+ name: str | None = None
74
+
75
+
76
+ @dataclass(frozen=True, slots=True)
77
+ class SystemAudioSource:
78
+ """Everything played on an output device (``None`` = default output)."""
79
+
80
+ device: AudioDevice | str | None = None
81
+ name: str | None = None
82
+
83
+
84
+ @dataclass(frozen=True, slots=True)
85
+ class ProcessAudioSource:
86
+ """Audio rendered by one process (and, by default, its child processes).
87
+
88
+ Never falls back to whole-system audio: if per-process capture is not
89
+ available, opening the source raises :class:`CapabilityUnavailableError`.
90
+ On Linux ``stream_ids`` may pin specific PulseAudio sink-inputs.
91
+ """
92
+
93
+ pid: int
94
+ include_children: bool = True
95
+ name: str | None = None
96
+ stream_ids: tuple[str, ...] = ()
97
+
98
+ @classmethod
99
+ def for_window(cls, window: Window, *, include_children: bool = True) -> ProcessAudioSource:
100
+ """Audio of the process that owns ``window``.
101
+
102
+ Note: the window's process is not always the process that plays the
103
+ audio (launchers, browser helper processes, audio-server proxies).
104
+ ``include_children`` covers the common child-process case; use
105
+ :func:`capturekit.list_audio_sessions` to pick a PID explicitly.
106
+ """
107
+ if window.pid is None:
108
+ from .errors import TargetNotFoundError
109
+
110
+ raise TargetNotFoundError(f"process of window {window.handle:#x} is unknown")
111
+ return cls(window.pid, include_children=include_children, name=window.process_name)
112
+
113
+
114
+ AudioSourceSpec = MicrophoneSource | SystemAudioSource | ProcessAudioSource
115
+
116
+
117
+ def audio_source_label(src: AudioSourceSpec) -> str:
118
+ if src.name:
119
+ return src.name
120
+ if isinstance(src, MicrophoneSource):
121
+ return "microphone"
122
+ if isinstance(src, SystemAudioSource):
123
+ return "system"
124
+ return f"process {src.pid}"
capturekit/types.py ADDED
@@ -0,0 +1,211 @@
1
+ """Plain data types shared by all backends.
2
+
3
+ All coordinates are physical pixels. Monitor rectangles are in virtual-desktop
4
+ coordinates (may be negative). Crop regions are always relative to the
5
+ selected source (monitor or window content).
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import enum
11
+ from dataclasses import dataclass, field
12
+ from typing import TYPE_CHECKING
13
+
14
+ from .errors import InvalidRegionError
15
+
16
+ if TYPE_CHECKING:
17
+ import numpy as np
18
+ import numpy.typing as npt
19
+
20
+
21
+ @dataclass(frozen=True, slots=True)
22
+ class Rect:
23
+ """Axis-aligned rectangle ``(x, y, width, height)``."""
24
+
25
+ x: int
26
+ y: int
27
+ width: int
28
+ height: int
29
+
30
+ @property
31
+ def right(self) -> int:
32
+ return self.x + self.width
33
+
34
+ @property
35
+ def bottom(self) -> int:
36
+ return self.y + self.height
37
+
38
+ @property
39
+ def size(self) -> tuple[int, int]:
40
+ return (self.width, self.height)
41
+
42
+ def is_empty(self) -> bool:
43
+ return self.width <= 0 or self.height <= 0
44
+
45
+ def intersect(self, other: Rect) -> Rect:
46
+ x = max(self.x, other.x)
47
+ y = max(self.y, other.y)
48
+ r = min(self.right, other.right)
49
+ b = min(self.bottom, other.bottom)
50
+ return Rect(x, y, max(0, r - x), max(0, b - y))
51
+
52
+ def contains(self, other: Rect) -> bool:
53
+ return (
54
+ other.x >= self.x
55
+ and other.y >= self.y
56
+ and other.right <= self.right
57
+ and other.bottom <= self.bottom
58
+ )
59
+
60
+ def translate(self, dx: int, dy: int) -> Rect:
61
+ return Rect(self.x + dx, self.y + dy, self.width, self.height)
62
+
63
+ @classmethod
64
+ def from_ltrb(cls, left: int, top: int, right: int, bottom: int) -> Rect:
65
+ return cls(left, top, right - left, bottom - top)
66
+
67
+
68
+ def validate_region(region: Rect, bounds_size: tuple[int, int]) -> Rect:
69
+ """Validate a crop region against a source of ``bounds_size``.
70
+
71
+ Raises :class:`InvalidRegionError` for empty or out-of-bounds regions.
72
+ """
73
+ if region.is_empty():
74
+ raise InvalidRegionError(f"crop region {region} is empty")
75
+ w, h = bounds_size
76
+ if region.x < 0 or region.y < 0 or region.right > w or region.bottom > h:
77
+ raise InvalidRegionError(f"crop region {region} lies outside the source ({w}x{h})")
78
+ return region
79
+
80
+
81
+ class PixelFormat(enum.Enum):
82
+ """Pixel layout of :attr:`Frame.data`. Only 8-bit BGRA is produced today."""
83
+
84
+ BGRA = "bgra"
85
+
86
+
87
+ @dataclass(frozen=True, slots=True)
88
+ class Monitor:
89
+ """A display output.
90
+
91
+ ``id`` is stable for the lifetime of the display configuration (Windows:
92
+ GDI device name such as ``\\\\.\\DISPLAY1``; X11: RandR monitor/output name).
93
+ """
94
+
95
+ id: str
96
+ index: int
97
+ name: str
98
+ rect: Rect
99
+ is_primary: bool
100
+ scale: float = 1.0
101
+ refresh_rate: float | None = None
102
+ display: str | None = None
103
+ native_handle: int = 0
104
+
105
+ @property
106
+ def width(self) -> int:
107
+ return self.rect.width
108
+
109
+ @property
110
+ def height(self) -> int:
111
+ return self.rect.height
112
+
113
+
114
+ @dataclass(frozen=True, slots=True)
115
+ class Window:
116
+ """A top-level window that can be targeted for exclusive capture.
117
+
118
+ ``handle`` is the native identifier (HWND on Windows, X11 window ID on
119
+ Linux). ``rect`` is the visible window bounds in desktop coordinates.
120
+ ``capture_excluded`` is True when the window asked the OS to exclude it
121
+ from capture (Windows display affinity); capturekit honours that.
122
+ """
123
+
124
+ handle: int
125
+ title: str
126
+ class_name: str | None
127
+ pid: int | None
128
+ process_name: str | None
129
+ rect: Rect
130
+ is_visible: bool
131
+ is_minimized: bool
132
+ display: str | None = None
133
+ capture_excluded: bool = False
134
+
135
+
136
+ class AudioDeviceKind(enum.Enum):
137
+ INPUT = "input" #: microphones / capture endpoints / Pulse sources
138
+ OUTPUT = "output" #: speakers / render endpoints (loopback-capable)
139
+
140
+
141
+ @dataclass(frozen=True, slots=True)
142
+ class AudioDevice:
143
+ id: str
144
+ name: str
145
+ kind: AudioDeviceKind
146
+ is_default: bool
147
+ channels: int | None = None
148
+ sample_rate: int | None = None
149
+ backend: str = ""
150
+
151
+
152
+ @dataclass(frozen=True, slots=True)
153
+ class AudioSession:
154
+ """An application audio stream/session and its owning process.
155
+
156
+ One process may own several sessions (several streams, several devices).
157
+ ``stream_id`` identifies the native stream where one exists (PulseAudio
158
+ sink-input index); Windows sessions are identified by ``pid``.
159
+ """
160
+
161
+ pid: int | None
162
+ process_name: str | None
163
+ display_name: str
164
+ device_id: str | None
165
+ is_active: bool
166
+ stream_id: str | None = None
167
+ backend: str = ""
168
+
169
+
170
+ @dataclass(slots=True)
171
+ class Frame:
172
+ """A captured video frame.
173
+
174
+ ``data`` is a ``(height, width, 4)`` uint8 BGRA array owned by the caller.
175
+ ``timestamp_ns`` is on the capturekit clock (see :func:`capturekit.now_ns`).
176
+ ``content_size`` is the source content size when the frame was produced
177
+ (useful to detect window resizes when a fixed output size is used).
178
+ """
179
+
180
+ data: npt.NDArray[np.uint8]
181
+ timestamp_ns: int
182
+ index: int
183
+ content_size: tuple[int, int]
184
+ pixel_format: PixelFormat = PixelFormat.BGRA
185
+
186
+ @property
187
+ def width(self) -> int:
188
+ return int(self.data.shape[1])
189
+
190
+ @property
191
+ def height(self) -> int:
192
+ return int(self.data.shape[0])
193
+
194
+
195
+ @dataclass(slots=True)
196
+ class AudioChunk:
197
+ """Interleaved float32 audio, shape ``(frames, channels)``."""
198
+
199
+ data: npt.NDArray[np.float32]
200
+ timestamp_ns: int
201
+ sample_rate: int
202
+ source: str = ""
203
+ flags: frozenset[str] = field(default_factory=frozenset)
204
+
205
+ @property
206
+ def frames(self) -> int:
207
+ return int(self.data.shape[0])
208
+
209
+ @property
210
+ def channels(self) -> int:
211
+ return int(self.data.shape[1])
@@ -0,0 +1,112 @@
1
+ Metadata-Version: 2.5
2
+ Name: capturekit
3
+ Version: 0.1.0
4
+ Summary: Python screen, exclusive-window and audio capture/recording for Windows and Linux X11
5
+ Author: capturekit contributors
6
+ License-Expression: MIT
7
+ Classifier: Development Status :: 3 - Alpha
8
+ Classifier: Operating System :: Microsoft :: Windows :: Windows 10
9
+ Classifier: Operating System :: Microsoft :: Windows :: Windows 11
10
+ Classifier: Operating System :: POSIX :: Linux
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Topic :: Multimedia :: Sound/Audio :: Capture/Recording
13
+ Classifier: Topic :: Multimedia :: Video :: Capture
14
+ Classifier: Typing :: Typed
15
+ Requires-Python: >=3.10
16
+ Requires-Dist: numpy>=1.24
17
+ Provides-Extra: dev
18
+ Requires-Dist: imageio-ffmpeg>=0.5; extra == 'dev'
19
+ Requires-Dist: mypy>=1.10; extra == 'dev'
20
+ Requires-Dist: pytest>=8; extra == 'dev'
21
+ Requires-Dist: ruff>=0.6; extra == 'dev'
22
+ Provides-Extra: ffmpeg
23
+ Requires-Dist: imageio-ffmpeg>=0.5; extra == 'ffmpeg'
24
+ Description-Content-Type: text/markdown
25
+
26
+ # capturekit
27
+
28
+ Python library for **monitor capture**, **exclusive window capture** (the
29
+ window's own pixels, even when covered by other windows) and **audio capture**
30
+ (microphone, system audio, *individual applications*), with synchronised
31
+ recording to H.264 MKV/MP4 through FFmpeg and hardware encoders.
32
+
33
+ * Windows 10/11 — DXGI Desktop Duplication, Windows Graphics Capture, WASAPI
34
+ (including per-process loopback). **Implemented and tested.**
35
+ * Linux X11 / Xvfb (explicit display strings such as `":99"`) — MIT-SHM,
36
+ XComposite, PulseAudio protocol (per-application capture via monitor
37
+ streams). **Implemented and tested on Xvfb**; native desktops/PipeWire
38
+ untested (see [capabilities](docs/capabilities.md)).
39
+ * Wayland-only sessions are detected and rejected; macOS is out of scope.
40
+
41
+ Pure Python + ctypes (no compiled extension). Runtime dependency: `numpy`.
42
+
43
+ ## Install
44
+
45
+ ```
46
+ pip install capturekit # library
47
+ pip install capturekit[ffmpeg] # + bundled FFmpeg binary (imageio-ffmpeg)
48
+ ```
49
+ Recording needs an FFmpeg executable: `PATH`, `CAPTUREKIT_FFMPEG`,
50
+ `EncoderConfig(ffmpeg_path=...)`, or the `[ffmpeg]` extra.
51
+
52
+ ## Quick start
53
+
54
+ ```python
55
+ import capturekit as ck
56
+
57
+ print(ck.get_capabilities().summary())
58
+
59
+ # One frame of the primary monitor (BGRA numpy array)
60
+ img = ck.screenshot()
61
+
62
+ # Exclusive capture of a window, cropped relative to its client area
63
+ win = next(w for w in ck.list_windows() if "Notepad" in w.title)
64
+ with ck.ScreenCapture(ck.WindowSource(win, client_area_only=True,
65
+ region=ck.Rect(0, 0, 400, 300))) as cap:
66
+ for frame in cap.frames(fps=30, max_frames=60):
67
+ process(frame.data) # (300, 400, 4) uint8
68
+
69
+ # Record the window + only that application's audio + the microphone
70
+ rec = ck.Recorder(
71
+ ck.WindowSource(win),
72
+ [ck.ProcessAudioSource.for_window(win), ck.MicrophoneSource()],
73
+ "notepad.mkv",
74
+ ck.RecordingConfig(fps=60, audio_mix=ck.AudioMixMode.BOTH),
75
+ on_error=print,
76
+ )
77
+ rec.start()
78
+ ...
79
+ rec.pause(); rec.resume()
80
+ result = rec.stop()
81
+ print(result.stats.video) # slots, new/duplicated/dropped frames, timings
82
+ ```
83
+
84
+ More: [examples/](examples/) · [API guide](docs/guide/api.md).
85
+
86
+ ## Documentation
87
+
88
+ | Topic | |
89
+ | --- | --- |
90
+ | What works where | [docs/capabilities.md](docs/capabilities.md) |
91
+ | Windows requirements & behaviour | [docs/guide/windows.md](docs/guide/windows.md) |
92
+ | Linux X11 / Xvfb | [docs/guide/linux.md](docs/guide/linux.md) |
93
+ | Audio sources, process selection | [docs/guide/audio.md](docs/guide/audio.md) |
94
+ | Encoders & hardware encoding | [docs/guide/encoding.md](docs/guide/encoding.md) |
95
+ | Performance numbers & tuning | [docs/guide/performance.md](docs/guide/performance.md) |
96
+ | Troubleshooting | [docs/guide/troubleshooting.md](docs/guide/troubleshooting.md) |
97
+ | Architecture / decisions | [docs/architecture.md](docs/architecture.md), [docs/decisions.md](docs/decisions.md) |
98
+ | Contributing | [CONTRIBUTING.md](CONTRIBUTING.md) |
99
+
100
+ ## Responsible use
101
+
102
+ Recording starts only through explicit API calls; capturekit has no
103
+ background, hidden or persistent recording features. Windows asking to be
104
+ excluded from capture (display affinity) and protected content are respected,
105
+ never bypassed. Windows draws its capture border unless you request otherwise
106
+ and the OS permits it.
107
+
108
+ ## Status
109
+
110
+ Alpha. Windows and Linux X11 backends are implemented and covered by
111
+ integration tests that run on real hardware (Windows) and private Xvfb
112
+ servers (Linux). See [docs/PROGRESS.md](docs/PROGRESS.md).
@@ -0,0 +1,41 @@
1
+ capturekit/__init__.py,sha256=Iuk2ziTdnweiM_32k2Ook_s1FDeVWRp3J4PaVeXcx8g,4374
2
+ capturekit/_platform.py,sha256=sz5IMyxhGR_Jxx5BDcddCWh5dBIYIV85jZypKbjI68c,717
3
+ capturekit/_version.py,sha256=kUR5RAFc7HCeiqdlX36dZOHkUI5wI6V_43RpEcD8b-0,22
4
+ capturekit/_worker.py,sha256=IUQzfQgadmaXLte-JJBO9poXacjd0Eropt9TIVBSsfg,3839
5
+ capturekit/capabilities.py,sha256=mckGANBvdL2DyqPqLBY-_oyBk2xNMBYVxhKuoIJybm8,2511
6
+ capturekit/capture.py,sha256=m6MgmaiaSjHzAV3v8Hqo_yULQYSWMmisMsL1LhPeLSQ,4925
7
+ capturekit/config.py,sha256=1el9fs3E--c9BE9cJFLe0X-K-Ll2kn0isjYI_3SYaWE,2821
8
+ capturekit/errors.py,sha256=52rpRLBjLOJPNMD4mK5MrgyoLiaaEpqXJDVtOOR-Bbg,3210
9
+ capturekit/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ capturekit/recording.py,sha256=KtlUunoQtSzW4T9UkllMP7qNShUczfR03CrwKLtPeOo,36693
11
+ capturekit/sources.py,sha256=SSYDbYkW_gujwOrXCBHEIZgByGjsJholi21TWz-H6T0,3877
12
+ capturekit/types.py,sha256=y_fzVmptwy3NeVfv7ezwOIg9Wb-EbLv6-mFEveV46tY,5717
13
+ capturekit/_backends/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
+ capturekit/_backends/base.py,sha256=Rgn2QAcISsyCPL4q6rsUH_lx74QPMVdNJ2yxsSxsmz0,2322
15
+ capturekit/_backends/linux/__init__.py,sha256=6JrAOfzfs9bx3w5R9cyLsXnCekdDQqcDK2Iml8uxiVc,55
16
+ capturekit/_backends/linux/_xlib.py,sha256=wz46UKohcwjg4fhHaaazDmbL4ckJ-G8aYsMNoSgRAms,20477
17
+ capturekit/_backends/linux/api.py,sha256=XZgMu8Z4yDomrdGE-ut79RD6uNoaGW8KaZpcqGzdmuc,5246
18
+ capturekit/_backends/linux/pulse.py,sha256=7sZ4nzto2XSyrIobKyzJ7qkdLZUNp52WKKCU37-cgxU,16713
19
+ capturekit/_backends/linux/x11.py,sha256=BrAaruKgtHSLJGSiZRXuw27h2xaNjptXDSs1u7gBgQs,27526
20
+ capturekit/_backends/windows/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
+ capturekit/_backends/windows/_com.py,sha256=J6o1EhdHTyhWp0UT56ofkglNiBnlnFKjhb_3_SyEIsc,11984
22
+ capturekit/_backends/windows/_d3d11.py,sha256=q6aQhLDoUepbP-hc2ltUSta9nLuiX-ai4n_0Qs8kcrU,10432
23
+ capturekit/_backends/windows/_win32.py,sha256=ALS2PY2tiOD4mQO7WX0uV1oQUkNdcOnglXoJHPLxF3g,14258
24
+ capturekit/_backends/windows/_winrt.py,sha256=J-UyZfsq03-_1Gz1_9Wf0h85blec0fRtZz-wYDD8uJE,5387
25
+ capturekit/_backends/windows/api.py,sha256=MfWkSpyCDtZnPHX_UX5D21cHLlIfoSchshSdhdLM54Y,7190
26
+ capturekit/_backends/windows/cursor.py,sha256=PEbWhYCK-anGL4mC0AxnHykdNvUPycFxWJJOhRzcdQM,2579
27
+ capturekit/_backends/windows/dxgi_duplication.py,sha256=XidQ0ra7ClOSGwq1w9eMt4lOSE2hzLE5EuVu015S0G4,12711
28
+ capturekit/_backends/windows/wasapi.py,sha256=Y2f-mwZlYUmaQHFX_S5CIEyU2cDc1I0Vk5wxFj1-oQ8,20874
29
+ capturekit/_backends/windows/wgc.py,sha256=ivjSW6S5IU8kcWsPAKHGVRfS8WQkgPTgbPin6dzQG9M,16566
30
+ capturekit/_pipeline/__init__.py,sha256=hAvywCElbVfWzkksc5MJ43sqd8TE4ET68ZusDeKep4I,77
31
+ capturekit/_pipeline/aligner.py,sha256=bqV9p-PTUt6uHugVxUG6aFLzJv-k5WFhSN_YiX5CSgs,6106
32
+ capturekit/_pipeline/clock.py,sha256=4yzZo7uDKXRpOAA9UYrdxKRa70ewxwUdSSYECEG_tpk,1480
33
+ capturekit/_pipeline/frame_queue.py,sha256=2ydgb51QKwfYC45tP6Lv7n6Koi0tfeJaUcRczYAsVIo,3606
34
+ capturekit/_pipeline/mixer.py,sha256=e4HCScOy-YMCpnofwJiEcBrqSMumhzbY02oowuan8VE,2510
35
+ capturekit/_pipeline/timeline.py,sha256=Exc8VPdHtGy-VFx2fOjSacN8KbjX72FWC7WstdO-Dvg,4840
36
+ capturekit/encoding/__init__.py,sha256=HHISkvMsrPdVXgsfl7RLsXSDcjyaF9ijmUIF3WgG0m8,234
37
+ capturekit/encoding/ffmpeg.py,sha256=whLjWrTPKh0WJU3Hw980DbKI0pj8eCCNWkMGhEGUzm8,12498
38
+ capturekit/encoding/pipes.py,sha256=99YvLPuddpUaHiPScUKA_aLMoLjlG_nKTb-VqfHZIkI,7334
39
+ capturekit-0.1.0.dist-info/METADATA,sha256=qMsdHurNeIvWTdw3UvL8AvQF2UF7u4V-gGd6gi2RiBY,4534
40
+ capturekit-0.1.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
41
+ capturekit-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any