memoir-node 0.1.5

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.
package/LICENSE ADDED
@@ -0,0 +1,27 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 lunavod
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ ---
24
+
25
+ This software links against FFmpeg, which is licensed under the
26
+ GNU Lesser General Public License (LGPL) version 2.1 or later.
27
+ See https://www.ffmpeg.org/legal.html for details.
package/README.md ADDED
@@ -0,0 +1,224 @@
1
+ # memoir-capture
2
+
3
+ Windows-native screen capture module with Python bindings for real-time frame analysis and deterministic replay recording.
4
+
5
+ Memoir captures frames from a window or monitor using Windows Graphics Capture (WGC), delivers them to Python as NumPy arrays, and optionally records them to HEVC video with per-frame metadata — all without GPU-to-CPU roundtrips in the recording path.
6
+
7
+ ## Features
8
+
9
+ - **WGC capture** — continuous frame capture from any window or monitor
10
+ - **NumPy delivery** — BGRA frames as `(H, W, 4)` uint8 arrays via bounded queue
11
+ - **Hardware-accelerated recording** — lossless HEVC encoding (YUV 4:4:4) via NVENC, AMF, or software x265 fallback
12
+ - **Binary metadata** — `.meta` sidecar with per-frame keyboard state, timestamps, and frame IDs
13
+ - **Dynamic recording** — start/stop recording without restarting capture
14
+ - **Frame-accurate keyboard** — key state snapshot at the exact moment each frame is accepted
15
+ - **Typed Python API** — full type annotations, dataclasses, context managers
16
+
17
+ ## Requirements
18
+
19
+ - Windows 10 1903+ (for WGC `CreateFreeThreaded`)
20
+ - Python 3.10+
21
+ - NVIDIA GPU (NVENC), AMD GPU (AMF), or CPU-only (x265 software fallback) for recording
22
+ - Visual Studio 2022 (for building from source)
23
+
24
+ ## Installation
25
+
26
+ ### From PyPI (prebuilt)
27
+
28
+ ```
29
+ pip install memoir-capture
30
+ ```
31
+
32
+ ### From source
33
+
34
+ ```powershell
35
+ # Clone with vcpkg
36
+ git clone https://github.com/lunavod/memoir-capture.git
37
+ cd Memoir
38
+ git clone https://github.com/microsoft/vcpkg.git vcpkg --depth 1
39
+ .\vcpkg\bootstrap-vcpkg.bat -disableMetrics
40
+
41
+ # Build
42
+ pip install build numpy
43
+ python -m build --wheel
44
+
45
+ # Install
46
+ pip install dist\memoir_capture-*.whl
47
+ ```
48
+
49
+ The first build takes ~15 minutes (vcpkg builds FFmpeg). Subsequent builds use cached binaries.
50
+
51
+ For local development without installing:
52
+
53
+ ```powershell
54
+ .\scripts\build.ps1
55
+ # memoir_capture/ package is ready to import from the project root
56
+ ```
57
+
58
+ ## Quick Start
59
+
60
+ ### Capture frames
61
+
62
+ ```python
63
+ import memoir_capture
64
+
65
+ engine = memoir_capture.CaptureEngine(
66
+ memoir_capture.MonitorTarget(0), # primary monitor
67
+ max_fps=10.0,
68
+ )
69
+ engine.start()
70
+
71
+ for packet in engine.frames():
72
+ with packet:
73
+ img = packet.cpu_bgra # numpy (H, W, 4) uint8
74
+ print(f"Frame {packet.frame_id}: {img.shape}")
75
+ print(f"Keys: 0x{packet.keyboard_mask:016x}")
76
+ break
77
+
78
+ engine.stop()
79
+ ```
80
+
81
+ ### Capture a specific window
82
+
83
+ ```python
84
+ engine = memoir_capture.CaptureEngine(
85
+ memoir_capture.WindowTitleTarget(r"(?i)notepad"),
86
+ max_fps=30.0,
87
+ )
88
+ ```
89
+
90
+ ### Record to MP4
91
+
92
+ ```python
93
+ engine = memoir_capture.CaptureEngine(
94
+ memoir_capture.MonitorTarget(0),
95
+ max_fps=10.0,
96
+ record_width=1920,
97
+ record_height=1080,
98
+ record_gop=1, # 1 = all-intra (frame-accurate seeking)
99
+ )
100
+ engine.start()
101
+
102
+ info = engine.start_recording("session_001")
103
+ print(f"Recording to {info.video_path}") # session_001.mp4
104
+
105
+ for i, packet in enumerate(engine.frames()):
106
+ packet.release()
107
+ if i >= 99:
108
+ break
109
+
110
+ engine.stop_recording() # finalizes .mp4 + .meta
111
+ engine.stop()
112
+ ```
113
+
114
+ ### Read metadata
115
+
116
+ ```python
117
+ meta = memoir_capture.MetaReader.read("session_001.meta")
118
+
119
+ print(f"Keys tracked: {[k.name for k in meta.keys]}")
120
+
121
+ for row in meta.rows:
122
+ print(f"Frame {row.frame_id}: keyboard=0x{row.keyboard_mask:016x}")
123
+ ```
124
+
125
+ ### Write metadata (for synthetic replays)
126
+
127
+ ```python
128
+ from memoir_capture import MetaWriter, MetaKeyEntry, MetaRow
129
+
130
+ keys = [MetaKeyEntry(0, 0x57, "W"), MetaKeyEntry(1, 0x41, "A")]
131
+
132
+ with MetaWriter("synthetic.meta", keys) as w:
133
+ w.write_row(MetaRow(
134
+ frame_id=0, record_frame_index=0,
135
+ capture_qpc=0, host_accept_qpc=0,
136
+ keyboard_mask=0b01,
137
+ width=1920, height=1080, analysis_stride=7680,
138
+ ))
139
+ ```
140
+
141
+ ### Context manager
142
+
143
+ ```python
144
+ with memoir_capture.CaptureEngine(memoir_capture.MonitorTarget(0)) as engine:
145
+ packet = engine.get_next_frame(timeout_ms=2000)
146
+ if packet:
147
+ with packet:
148
+ process(packet.cpu_bgra)
149
+ ```
150
+
151
+ ## API Reference
152
+
153
+ ### `CaptureEngine(target, *, max_fps, ...)`
154
+
155
+ | Parameter | Default | Description |
156
+ |-----------|---------|-------------|
157
+ | `target` | required | `MonitorTarget(index)`, `WindowTitleTarget(regex)`, or `WindowExeTarget(regex)` |
158
+ | `max_fps` | `10.0` | Maximum accepted frame rate |
159
+ | `analysis_queue_capacity` | `1` | Bounded queue size for Python delivery |
160
+ | `capture_cursor` | `False` | Include cursor in capture |
161
+ | `record_width` | `1920` | Recording output width |
162
+ | `record_height` | `1080` | Recording output height |
163
+ | `record_gop` | `1` | GOP size (1 = all-intra, higher = smaller files) |
164
+
165
+ **Methods**: `start()`, `stop()`, `get_next_frame(timeout_ms)`, `frames()`, `start_recording(base_path, *, encoder=None)`, `stop_recording()`, `is_recording()`, `stats()`
166
+
167
+ ### `FramePacket`
168
+
169
+ | Property | Type | Description |
170
+ |----------|------|-------------|
171
+ | `frame_id` | `int` | Monotonic ID (only accepted frames get IDs) |
172
+ | `cpu_bgra` | `np.ndarray` | `(H, W, 4)` uint8 BGRA pixel data |
173
+ | `keyboard_mask` | `int` | 64-bit bitmask of tracked keys |
174
+ | `capture_qpc` | `int` | WGC capture timestamp (100ns units) |
175
+ | `width`, `height`, `stride` | `int` | Frame dimensions |
176
+
177
+ Supports `with packet:` (auto-release) and explicit `packet.release()`.
178
+
179
+ ### Recording
180
+
181
+ Encoding: lossless HEVC (QP=0), YUV 4:4:4, full color range. The encoder is selected automatically in priority order: `hevc_nvenc` (NVIDIA) → `hevc_amf` (AMD) → `libx265` (software). You can force a specific encoder:
182
+
183
+ ```python
184
+ info = engine.start_recording("session_001", encoder="libx265")
185
+ ```
186
+
187
+ | Setting | File size (10s, 1080p) |
188
+ |---------|----------------------|
189
+ | GOP=1 (all-intra) | ~52 MB |
190
+ | GOP=10 | ~17 MB |
191
+ | GOP=30 | ~14 MB |
192
+
193
+ ## Architecture
194
+
195
+ ```
196
+ WGC FrameArrived (thread pool)
197
+
198
+ ├─ FPS limiter → drop if too soon
199
+ ├─ Queue check → drop if full (drop-new policy)
200
+
201
+ ├─ Accept: assign frame_id, snapshot keyboard
202
+ ├─ GPU→CPU: CopyResource → staging → Map → memcpy
203
+ ├─ Recording: swscale (BGRA→YUV444) → HEVC encoder → MP4
204
+ └─ Enqueue → Python consumer
205
+ ```
206
+
207
+ - Single `ID3D11DeviceContext` + mutex for all GPU ops
208
+ - WGC `CreateFreeThreaded` — no DispatcherQueue needed
209
+ - Recording runs on the callback thread (well within budget at 10fps)
210
+ - `FramePacket` owns its pixel buffer; `release()` frees it
211
+
212
+ ## Testing
213
+
214
+ ```powershell
215
+ # Full suite (requires display + supported GPU or x265)
216
+ pytest -v
217
+
218
+ # Headless (CI-safe)
219
+ pytest --headless -v
220
+ ```
221
+
222
+ ## License
223
+
224
+ MIT. Links against FFmpeg (LGPL 2.1+).
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,3 @@
1
+ export type { CaptureTarget, MonitorTarget, WindowTitleTarget, WindowExeTarget, KeySpec, EngineOptions, FramePacket, RecordingInfo, EngineStats, MetaHeader, MetaKeyEntry, MetaRow, MetaFile, } from './types';
2
+ export { readMeta, writeMeta, isPressed, pressedKeys, synthesizeKeyEvents } from './meta';
3
+ export { CaptureEngine, ping, version, grab } from './native';
package/dist/index.js ADDED
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.grab = exports.version = exports.ping = exports.CaptureEngine = exports.synthesizeKeyEvents = exports.pressedKeys = exports.isPressed = exports.writeMeta = exports.readMeta = void 0;
4
+ // Pure TypeScript meta reader/writer
5
+ var meta_1 = require("./meta");
6
+ Object.defineProperty(exports, "readMeta", { enumerable: true, get: function () { return meta_1.readMeta; } });
7
+ Object.defineProperty(exports, "writeMeta", { enumerable: true, get: function () { return meta_1.writeMeta; } });
8
+ Object.defineProperty(exports, "isPressed", { enumerable: true, get: function () { return meta_1.isPressed; } });
9
+ Object.defineProperty(exports, "pressedKeys", { enumerable: true, get: function () { return meta_1.pressedKeys; } });
10
+ Object.defineProperty(exports, "synthesizeKeyEvents", { enumerable: true, get: function () { return meta_1.synthesizeKeyEvents; } });
11
+ // Native addon
12
+ var native_1 = require("./native");
13
+ Object.defineProperty(exports, "CaptureEngine", { enumerable: true, get: function () { return native_1.CaptureEngine; } });
14
+ Object.defineProperty(exports, "ping", { enumerable: true, get: function () { return native_1.ping; } });
15
+ Object.defineProperty(exports, "version", { enumerable: true, get: function () { return native_1.version; } });
16
+ Object.defineProperty(exports, "grab", { enumerable: true, get: function () { return native_1.grab; } });
package/dist/meta.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ import type { MetaFile, MetaKeyEntry, MetaRow } from './types';
2
+ export declare function readMeta(path: string): MetaFile;
3
+ export declare function writeMeta(path: string, keys: MetaKeyEntry[], rows: MetaRow[], createdUnixNs?: bigint): void;
4
+ export declare function isPressed(row: MetaRow, keyName: string, keys: MetaKeyEntry[]): boolean;
5
+ export declare function pressedKeys(row: MetaRow, keys: MetaKeyEntry[]): string[];
6
+ export declare function synthesizeKeyEvents(rows: MetaRow[], keys: MetaKeyEntry[]): Array<{
7
+ frame: bigint;
8
+ type: 'keyDown' | 'keyUp';
9
+ key: string;
10
+ }>;
package/dist/meta.js ADDED
@@ -0,0 +1,119 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readMeta = readMeta;
4
+ exports.writeMeta = writeMeta;
5
+ exports.isPressed = isPressed;
6
+ exports.pressedKeys = pressedKeys;
7
+ exports.synthesizeKeyEvents = synthesizeKeyEvents;
8
+ const fs_1 = require("fs");
9
+ const HEADER_SIZE = 32;
10
+ const KEY_SIZE = 40;
11
+ const ROW_SIZE = 56;
12
+ const MAGIC = Buffer.from('RCMETA1\x00', 'ascii');
13
+ function readMeta(path) {
14
+ const buf = (0, fs_1.readFileSync)(path);
15
+ const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
16
+ // Header
17
+ const magic = buf.subarray(0, 8);
18
+ if (!magic.equals(MAGIC))
19
+ throw new Error(`Bad magic: ${magic.toString('hex')}`);
20
+ const version = view.getUint32(8, true);
21
+ const createdUnixNs = view.getBigUint64(16, true);
22
+ const keyCount = view.getUint32(24, true);
23
+ // Keys
24
+ let offset = HEADER_SIZE;
25
+ const keys = [];
26
+ for (let i = 0; i < keyCount; i++) {
27
+ const bit = view.getUint32(offset, true);
28
+ const vk = view.getUint32(offset + 4, true);
29
+ const nameBytes = buf.subarray(offset + 8, offset + 40);
30
+ const nullIdx = nameBytes.indexOf(0);
31
+ const name = nameBytes.subarray(0, nullIdx === -1 ? 32 : nullIdx).toString('ascii');
32
+ keys.push({ bit, vk, name });
33
+ offset += KEY_SIZE;
34
+ }
35
+ // Rows
36
+ const rows = [];
37
+ while (offset + ROW_SIZE <= buf.length) {
38
+ rows.push({
39
+ frameId: view.getBigUint64(offset, true),
40
+ recordFrameIndex: view.getBigUint64(offset + 8, true),
41
+ captureQpc: view.getBigInt64(offset + 16, true),
42
+ hostAcceptQpc: view.getBigInt64(offset + 24, true),
43
+ keyboardMask: view.getBigUint64(offset + 32, true),
44
+ width: view.getUint32(offset + 40, true),
45
+ height: view.getUint32(offset + 44, true),
46
+ analysisStride: view.getUint32(offset + 48, true),
47
+ flags: view.getUint32(offset + 52, true),
48
+ });
49
+ offset += ROW_SIZE;
50
+ }
51
+ return {
52
+ header: { version, createdUnixNs, keyCount },
53
+ keys,
54
+ rows,
55
+ };
56
+ }
57
+ function writeMeta(path, keys, rows, createdUnixNs) {
58
+ const size = HEADER_SIZE + keys.length * KEY_SIZE + rows.length * ROW_SIZE;
59
+ const buf = Buffer.alloc(size);
60
+ const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
61
+ // Header
62
+ MAGIC.copy(buf, 0);
63
+ view.setUint32(8, 1, true); // version
64
+ view.setBigUint64(16, createdUnixNs ?? BigInt(Date.now()) * 1000000n, true);
65
+ view.setUint32(24, keys.length, true);
66
+ // Keys
67
+ let offset = HEADER_SIZE;
68
+ for (const k of keys) {
69
+ view.setUint32(offset, k.bit, true);
70
+ view.setUint32(offset + 4, k.vk, true);
71
+ buf.write(k.name.substring(0, 31), offset + 8, 'ascii');
72
+ offset += KEY_SIZE;
73
+ }
74
+ // Rows
75
+ for (const r of rows) {
76
+ view.setBigUint64(offset, r.frameId, true);
77
+ view.setBigUint64(offset + 8, r.recordFrameIndex, true);
78
+ view.setBigInt64(offset + 16, r.captureQpc, true);
79
+ view.setBigInt64(offset + 24, r.hostAcceptQpc, true);
80
+ view.setBigUint64(offset + 32, r.keyboardMask, true);
81
+ view.setUint32(offset + 40, r.width, true);
82
+ view.setUint32(offset + 44, r.height, true);
83
+ view.setUint32(offset + 48, r.analysisStride, true);
84
+ view.setUint32(offset + 52, r.flags, true);
85
+ offset += ROW_SIZE;
86
+ }
87
+ (0, fs_1.writeFileSync)(path, buf);
88
+ }
89
+ // ─── Helpers ────────────────────────────────────────────────────
90
+ function isPressed(row, keyName, keys) {
91
+ const key = keys.find(k => k.name === keyName);
92
+ if (!key)
93
+ throw new Error(`Key "${keyName}" not in key map`);
94
+ return (row.keyboardMask & (1n << BigInt(key.bit))) !== 0n;
95
+ }
96
+ function pressedKeys(row, keys) {
97
+ return keys.filter(k => (row.keyboardMask & (1n << BigInt(k.bit))) !== 0n).map(k => k.name);
98
+ }
99
+ function synthesizeKeyEvents(rows, keys) {
100
+ const events = [];
101
+ let prevMask = 0n;
102
+ for (const row of rows) {
103
+ const diff = row.keyboardMask ^ prevMask;
104
+ if (diff !== 0n) {
105
+ for (const k of keys) {
106
+ const bit = 1n << BigInt(k.bit);
107
+ if ((diff & bit) !== 0n) {
108
+ events.push({
109
+ frame: row.recordFrameIndex,
110
+ type: (row.keyboardMask & bit) !== 0n ? 'keyDown' : 'keyUp',
111
+ key: k.name,
112
+ });
113
+ }
114
+ }
115
+ }
116
+ prevMask = row.keyboardMask;
117
+ }
118
+ return events;
119
+ }
@@ -0,0 +1,29 @@
1
+ import type { CaptureTarget, EngineOptions, FramePacket } from './types';
2
+ export declare const CaptureEngine: {
3
+ new (options: EngineOptions): {
4
+ start(): void;
5
+ stop(): void;
6
+ getNextFrame(timeoutMs?: number): FramePacket | null;
7
+ startRecording(basePath: string, encoder?: string): import('./types').RecordingInfo;
8
+ startRecording(opts: {
9
+ path: string;
10
+ videoName: string;
11
+ metaName: string;
12
+ encoder?: string;
13
+ }): import('./types').RecordingInfo;
14
+ stopRecording(): void;
15
+ isRecording(): boolean;
16
+ stats(): import('./types').EngineStats;
17
+ lastError(): string | null;
18
+ };
19
+ };
20
+ export declare const ping: () => string;
21
+ export declare const version: string;
22
+ /**
23
+ * Capture a single frame and return it.
24
+ * Creates a temporary engine, grabs one frame, stops.
25
+ */
26
+ export declare function grab(target: CaptureTarget, opts?: {
27
+ timeoutMs?: number;
28
+ maxFps?: number;
29
+ }): FramePacket;
package/dist/native.js ADDED
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.version = exports.ping = exports.CaptureEngine = void 0;
7
+ exports.grab = grab;
8
+ const path_1 = __importDefault(require("path"));
9
+ // Load the compiled N-API addon
10
+ let addon;
11
+ try {
12
+ addon = require(path_1.default.resolve(__dirname, '../build/Release/memoir_node.node'));
13
+ }
14
+ catch {
15
+ // Fallback for different build configurations
16
+ addon = require(path_1.default.resolve(__dirname, '../build/Debug/memoir_node.node'));
17
+ }
18
+ exports.CaptureEngine = addon.CaptureEngine;
19
+ exports.ping = addon.ping;
20
+ exports.version = addon.version;
21
+ /**
22
+ * Capture a single frame and return it.
23
+ * Creates a temporary engine, grabs one frame, stops.
24
+ */
25
+ function grab(target, opts) {
26
+ const engine = new exports.CaptureEngine({ target, maxFps: opts?.maxFps ?? 60 });
27
+ engine.start();
28
+ try {
29
+ const frame = engine.getNextFrame(opts?.timeoutMs ?? 5000);
30
+ if (!frame)
31
+ throw new Error('No frame captured within timeout');
32
+ return frame;
33
+ }
34
+ finally {
35
+ engine.stop();
36
+ }
37
+ }
@@ -0,0 +1,83 @@
1
+ export interface MonitorTarget {
2
+ type: 'monitor';
3
+ index: number;
4
+ }
5
+ export interface WindowTitleTarget {
6
+ type: 'windowTitle';
7
+ pattern: string;
8
+ }
9
+ export interface WindowExeTarget {
10
+ type: 'windowExe';
11
+ pattern: string;
12
+ }
13
+ export type CaptureTarget = MonitorTarget | WindowTitleTarget | WindowExeTarget;
14
+ export interface KeySpec {
15
+ bit: number;
16
+ vk: number;
17
+ name: string;
18
+ }
19
+ export interface EngineOptions {
20
+ target: CaptureTarget;
21
+ maxFps?: number;
22
+ queueCapacity?: number;
23
+ captureCursor?: boolean;
24
+ keys?: KeySpec[];
25
+ recordWidth?: number;
26
+ recordHeight?: number;
27
+ recordGop?: number;
28
+ }
29
+ export interface FramePacket {
30
+ readonly frameId: number;
31
+ readonly width: number;
32
+ readonly height: number;
33
+ readonly stride: number;
34
+ readonly captureQpc: bigint;
35
+ readonly hostAcceptQpc: bigint;
36
+ readonly keyboardMask: bigint;
37
+ readonly data: Buffer;
38
+ readonly released: boolean;
39
+ release(): void;
40
+ }
41
+ export interface RecordingInfo {
42
+ basePath: string;
43
+ videoPath: string;
44
+ metaPath: string;
45
+ codec: string;
46
+ width: number;
47
+ height: number;
48
+ }
49
+ export interface EngineStats {
50
+ framesSeen: number;
51
+ framesAccepted: number;
52
+ framesDroppedQueueFull: number;
53
+ framesDroppedError: number;
54
+ framesRecorded: number;
55
+ queueDepth: number;
56
+ recording: boolean;
57
+ }
58
+ export interface MetaHeader {
59
+ version: number;
60
+ createdUnixNs: bigint;
61
+ keyCount: number;
62
+ }
63
+ export interface MetaKeyEntry {
64
+ bit: number;
65
+ vk: number;
66
+ name: string;
67
+ }
68
+ export interface MetaRow {
69
+ frameId: bigint;
70
+ recordFrameIndex: bigint;
71
+ captureQpc: bigint;
72
+ hostAcceptQpc: bigint;
73
+ keyboardMask: bigint;
74
+ width: number;
75
+ height: number;
76
+ analysisStride: number;
77
+ flags: number;
78
+ }
79
+ export interface MetaFile {
80
+ header: MetaHeader;
81
+ keys: MetaKeyEntry[];
82
+ rows: MetaRow[];
83
+ }
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ // ─── Capture Targets ────────────────────────────────────────────
3
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "memoir-node",
3
+ "version": "0.1.5",
4
+ "description": "Windows-native screen capture with Node.js bindings",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/lunavod/memoir-capture.git"
9
+ },
10
+ "os": ["win32"],
11
+ "cpu": ["x64"],
12
+ "engines": {
13
+ "node": ">=18"
14
+ },
15
+ "keywords": ["screen-capture", "windows", "native", "napi", "wgc", "ffmpeg", "recording"],
16
+ "main": "dist/index.js",
17
+ "types": "dist/index.d.ts",
18
+ "scripts": {
19
+ "build:native": "cmake-js build --CDMEMOIR_BUILD_NODE=ON --CDMEMOIR_BUILD_PYTHON=OFF --CDCMAKE_TOOLCHAIN_FILE=vcpkg/scripts/buildsystems/vcpkg.cmake --CDVCPKG_OVERLAY_TRIPLETS=cmake/triplets --CDVCPKG_TARGET_TRIPLET=x64-windows-release",
20
+ "build:ts": "tsc",
21
+ "build": "npm run build:native && tsc",
22
+ "rebuild": "cmake-js rebuild --CDMEMOIR_BUILD_NODE=ON --CDMEMOIR_BUILD_PYTHON=OFF --CDCMAKE_TOOLCHAIN_FILE=vcpkg/scripts/buildsystems/vcpkg.cmake --CDVCPKG_OVERLAY_TRIPLETS=cmake/triplets --CDVCPKG_TARGET_TRIPLET=x64-windows-release",
23
+ "test": "vitest run"
24
+ },
25
+ "dependencies": {},
26
+ "devDependencies": {
27
+ "@types/node": "^22.0.0",
28
+ "cmake-js": "^7.0.0",
29
+ "node-addon-api": "^7.0.0",
30
+ "typescript": "^5.5.0",
31
+ "vitest": "^3.0.0"
32
+ },
33
+ "files": [
34
+ "dist/*.js",
35
+ "dist/*.d.ts",
36
+ "build/Release/memoir_node.node",
37
+ "build/Release/*.dll",
38
+ "LICENSE"
39
+ ]
40
+ }