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.
- capturekit/__init__.py +164 -0
- capturekit/_backends/__init__.py +0 -0
- capturekit/_backends/base.py +89 -0
- capturekit/_backends/linux/__init__.py +1 -0
- capturekit/_backends/linux/_xlib.py +655 -0
- capturekit/_backends/linux/api.py +160 -0
- capturekit/_backends/linux/pulse.py +464 -0
- capturekit/_backends/linux/x11.py +720 -0
- capturekit/_backends/windows/__init__.py +0 -0
- capturekit/_backends/windows/_com.py +350 -0
- capturekit/_backends/windows/_d3d11.py +307 -0
- capturekit/_backends/windows/_win32.py +420 -0
- capturekit/_backends/windows/_winrt.py +162 -0
- capturekit/_backends/windows/api.py +214 -0
- capturekit/_backends/windows/cursor.py +66 -0
- capturekit/_backends/windows/dxgi_duplication.py +310 -0
- capturekit/_backends/windows/wasapi.py +570 -0
- capturekit/_backends/windows/wgc.py +398 -0
- capturekit/_pipeline/__init__.py +1 -0
- capturekit/_pipeline/aligner.py +177 -0
- capturekit/_pipeline/clock.py +47 -0
- capturekit/_pipeline/frame_queue.py +120 -0
- capturekit/_pipeline/mixer.py +80 -0
- capturekit/_pipeline/timeline.py +133 -0
- capturekit/_platform.py +24 -0
- capturekit/_version.py +1 -0
- capturekit/_worker.py +100 -0
- capturekit/capabilities.py +79 -0
- capturekit/capture.py +141 -0
- capturekit/config.py +85 -0
- capturekit/encoding/__init__.py +5 -0
- capturekit/encoding/ffmpeg.py +356 -0
- capturekit/encoding/pipes.py +212 -0
- capturekit/errors.py +109 -0
- capturekit/py.typed +0 -0
- capturekit/recording.py +940 -0
- capturekit/sources.py +124 -0
- capturekit/types.py +211 -0
- capturekit-0.1.0.dist-info/METADATA +112 -0
- capturekit-0.1.0.dist-info/RECORD +41 -0
- 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,,
|