pup-recorder 0.0.8
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/Cargo.lock +230 -0
- package/Cargo.toml +23 -0
- package/LICENSE +26 -0
- package/README.md +96 -0
- package/build.rs +5 -0
- package/build.ts +55 -0
- package/build_rust.ts +51 -0
- package/dist/cjs/app.cjs +633 -0
- package/dist/cjs/app.cjs.map +7 -0
- package/dist/cjs/cli.cjs +691 -0
- package/dist/cjs/cli.cjs.map +7 -0
- package/dist/cjs/index.cjs +781 -0
- package/dist/cjs/index.cjs.map +7 -0
- package/dist/cli.js +667 -0
- package/dist/cli.js.map +7 -0
- package/dist/index.d.ts +93 -0
- package/dist/index.js +728 -0
- package/dist/index.js.map +7 -0
- package/package.json +37 -0
- package/rust/darwin-arm64.node +0 -0
- package/rust/darwin-x64.node +0 -0
- package/rust/linux-arm64.node +0 -0
- package/rust/linux-x64.node +0 -0
- package/src/app.ts +19 -0
- package/src/base/abort.ts +75 -0
- package/src/base/constants.ts +18 -0
- package/src/base/electron.ts +51 -0
- package/src/base/encoder.ts +35 -0
- package/src/base/env.ts +21 -0
- package/src/base/ffmpeg.ts +188 -0
- package/src/base/frame_sync.ts +139 -0
- package/src/base/image.ts +9 -0
- package/src/base/lazy.ts +20 -0
- package/src/base/limiter.ts +58 -0
- package/src/base/logging.ts +123 -0
- package/src/base/noerr.ts +18 -0
- package/src/base/parser.ts +12 -0
- package/src/base/process.ts +35 -0
- package/src/base/proxy.ts +33 -0
- package/src/base/record.ts +228 -0
- package/src/base/retry.ts +40 -0
- package/src/base/stream.ts +74 -0
- package/src/base/timing.ts +23 -0
- package/src/base/types.ts +19 -0
- package/src/cli.ts +6 -0
- package/src/common.ts +53 -0
- package/src/index.ts +14 -0
- package/src/pup.ts +142 -0
- package/src/rust/lib.rs +105 -0
- package/src/rust/lib.ts +28 -0
- package/tsconfig.json +25 -0
- package/x265/darwin-arm64 +0 -0
- package/x265/darwin-x64 +0 -0
- package/x265/linux-arm64 +0 -0
- package/x265/linux-x64 +0 -0
package/dist/cjs/app.cjs
ADDED
|
@@ -0,0 +1,633 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
+
for (let key of __getOwnPropNames(from))
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
12
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
13
|
+
}
|
|
14
|
+
return to;
|
|
15
|
+
};
|
|
16
|
+
var __toESM = (mod2, isNodeMode, target) => (target = mod2 != null ? __create(__getProtoOf(mod2)) : {}, __copyProps(
|
|
17
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
18
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
19
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
20
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
21
|
+
isNodeMode || !mod2 || !mod2.__esModule ? __defProp(target, "default", { value: mod2, enumerable: true }) : target,
|
|
22
|
+
mod2
|
|
23
|
+
));
|
|
24
|
+
|
|
25
|
+
// src/app.ts
|
|
26
|
+
var import_electron4 = require("electron");
|
|
27
|
+
|
|
28
|
+
// src/base/electron.ts
|
|
29
|
+
var import_electron = __toESM(require("electron"), 1);
|
|
30
|
+
|
|
31
|
+
// src/base/constants.ts
|
|
32
|
+
var import_fs = require("fs");
|
|
33
|
+
var import_path = require("path");
|
|
34
|
+
|
|
35
|
+
// src/base/env.ts
|
|
36
|
+
function penv(name, parser, defaultValue) {
|
|
37
|
+
try {
|
|
38
|
+
return parser(process.env[name]);
|
|
39
|
+
} catch {
|
|
40
|
+
return defaultValue;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// src/base/parser.ts
|
|
45
|
+
function parseNumber(value) {
|
|
46
|
+
if (typeof value === "number") {
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
const num = Number(value);
|
|
50
|
+
if (Number.isNaN(num)) {
|
|
51
|
+
throw new Error(`Value ${value} is not a valid number`);
|
|
52
|
+
}
|
|
53
|
+
return num;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/base/constants.ts
|
|
57
|
+
var pupAppSearchPaths = [
|
|
58
|
+
(0, import_path.resolve)(__dirname, "cjs/app.cjs"),
|
|
59
|
+
// process from dist
|
|
60
|
+
(0, import_path.resolve)(__dirname, "app.cjs"),
|
|
61
|
+
// process from dist/cjs
|
|
62
|
+
(0, import_path.resolve)(__dirname, "../../cjs/app.cjs")
|
|
63
|
+
// process from src
|
|
64
|
+
];
|
|
65
|
+
var pupAppPath = pupAppSearchPaths.find(import_fs.existsSync);
|
|
66
|
+
var env = process.env;
|
|
67
|
+
var pupLogLevel = penv("PUP_LOG_LEVEL", parseNumber, 2);
|
|
68
|
+
var pupUseInnerProxy = env["PUP_USE_INNER_PROXY"] === "1";
|
|
69
|
+
var pupFFmpegPath = env["FFMPEG_BIN"] ?? `ffmpeg`;
|
|
70
|
+
|
|
71
|
+
// src/base/logging.ts
|
|
72
|
+
var DEBUG = "<pup@debug>";
|
|
73
|
+
var INFO = "<pup@info>";
|
|
74
|
+
var WARN = "<pup@warn>";
|
|
75
|
+
var ERROR = "<pup@error>";
|
|
76
|
+
var FATAL = "<pup@fatal>";
|
|
77
|
+
var Logger = class {
|
|
78
|
+
_impl;
|
|
79
|
+
get impl() {
|
|
80
|
+
return this._impl;
|
|
81
|
+
}
|
|
82
|
+
set impl(value) {
|
|
83
|
+
const debug = value.debug ?? console.debug;
|
|
84
|
+
const info = value.info ?? console.info;
|
|
85
|
+
const warn = value.warn ?? console.warn;
|
|
86
|
+
const error = value.error ?? console.error;
|
|
87
|
+
this._impl = {
|
|
88
|
+
debug: pupLogLevel >= 3 ? debug : void 0,
|
|
89
|
+
info: pupLogLevel >= 2 ? info : void 0,
|
|
90
|
+
warn: pupLogLevel >= 1 ? warn : void 0,
|
|
91
|
+
error: pupLogLevel >= 0 ? error : void 0
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
constructor() {
|
|
95
|
+
this.impl = console;
|
|
96
|
+
}
|
|
97
|
+
debug(...messages) {
|
|
98
|
+
this.impl?.debug?.(DEBUG, ...messages);
|
|
99
|
+
}
|
|
100
|
+
info(...messages) {
|
|
101
|
+
this.impl?.info?.(INFO, ...messages);
|
|
102
|
+
}
|
|
103
|
+
warn(...messages) {
|
|
104
|
+
this.impl?.warn?.(WARN, ...messages);
|
|
105
|
+
}
|
|
106
|
+
error(...messages) {
|
|
107
|
+
this.impl?.error?.(ERROR, ...messages);
|
|
108
|
+
}
|
|
109
|
+
fatal(...messages) {
|
|
110
|
+
this.impl?.error?.(FATAL, ...messages);
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
dispatch(message) {
|
|
114
|
+
if (message.startsWith(DEBUG)) {
|
|
115
|
+
this.debug(message.slice(DEBUG.length + 1));
|
|
116
|
+
} else if (message.startsWith(INFO)) {
|
|
117
|
+
this.info(message.slice(INFO.length + 1));
|
|
118
|
+
} else if (message.startsWith(WARN)) {
|
|
119
|
+
this.warn(message.slice(WARN.length + 1));
|
|
120
|
+
} else if (message.startsWith(ERROR)) {
|
|
121
|
+
this.error(message.slice(ERROR.length + 1));
|
|
122
|
+
} else {
|
|
123
|
+
this.info(message);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
attach(proc, name) {
|
|
127
|
+
return new Promise((resolve2, reject) => {
|
|
128
|
+
this.debug(`${name}.attach`);
|
|
129
|
+
let fatal = "";
|
|
130
|
+
const dispatch = (data) => {
|
|
131
|
+
const message = data.toString();
|
|
132
|
+
if (message.startsWith(FATAL)) {
|
|
133
|
+
fatal += message.slice(FATAL.length + 1);
|
|
134
|
+
} else {
|
|
135
|
+
this.dispatch(message);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
proc.stderr?.on("data", dispatch);
|
|
139
|
+
proc.stdout?.on("data", dispatch);
|
|
140
|
+
proc.on("message", dispatch).on("error", (err) => {
|
|
141
|
+
fatal += err.message;
|
|
142
|
+
proc.kill();
|
|
143
|
+
}).once("close", (code, signal) => {
|
|
144
|
+
if (code || signal || fatal) {
|
|
145
|
+
fatal ||= `command failed: ${proc.spawnargs.join(" ")}`;
|
|
146
|
+
this.error(`${name}.close`, { code, signal, fatal });
|
|
147
|
+
reject(new Error(fatal));
|
|
148
|
+
} else {
|
|
149
|
+
this.debug(`${name}.close`);
|
|
150
|
+
resolve2();
|
|
151
|
+
}
|
|
152
|
+
}).on("unhandledRejection", (reason) => {
|
|
153
|
+
this.error(`${name}.unhandled`, reason);
|
|
154
|
+
}).on("uncaughtExceptionMonitor", (err) => {
|
|
155
|
+
this.error(`${name}.unhandled`, err);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
var logger = new Logger();
|
|
161
|
+
|
|
162
|
+
// src/base/process.ts
|
|
163
|
+
var PUP_ARGS_ENV_KEY = "__PUP_ARGS__";
|
|
164
|
+
function pargs() {
|
|
165
|
+
const pupArgs = process.env[PUP_ARGS_ENV_KEY];
|
|
166
|
+
if (pupArgs) {
|
|
167
|
+
const args = ["exec", ...process.argv.slice(-1)];
|
|
168
|
+
args.push(...JSON.parse(pupArgs));
|
|
169
|
+
logger.debug("pupargs", args);
|
|
170
|
+
return args;
|
|
171
|
+
}
|
|
172
|
+
logger.debug("procargv", process.argv);
|
|
173
|
+
return process.argv;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// src/base/electron.ts
|
|
177
|
+
var ELECTRON_OPTS = [
|
|
178
|
+
"no-sandbox",
|
|
179
|
+
"disable-setuid-sandbox",
|
|
180
|
+
"disable-gpu",
|
|
181
|
+
"disable-dev-shm-usage",
|
|
182
|
+
"disable-software-rasterizer",
|
|
183
|
+
"disable-web-security",
|
|
184
|
+
"disable-site-isolation-trials",
|
|
185
|
+
"disable-features=IsolateOrigins,site-per-process",
|
|
186
|
+
"allow-insecure-localhost",
|
|
187
|
+
"ignore-certificate-errors",
|
|
188
|
+
"disable-blink-features=AutomationControlled",
|
|
189
|
+
"mute-audio",
|
|
190
|
+
"disable-extensions",
|
|
191
|
+
"disable-background-networking",
|
|
192
|
+
"address-family=ipv4",
|
|
193
|
+
"disable-async-dns",
|
|
194
|
+
"force-device-scale-factor=1",
|
|
195
|
+
"trace-warnings",
|
|
196
|
+
"force-color-profile=srgb",
|
|
197
|
+
"disable-color-correct-rendering",
|
|
198
|
+
"log-level=3"
|
|
199
|
+
];
|
|
200
|
+
|
|
201
|
+
// src/base/record.ts
|
|
202
|
+
var import_electron3 = require("electron");
|
|
203
|
+
var import_promises2 = require("fs/promises");
|
|
204
|
+
var import_path3 = require("path");
|
|
205
|
+
|
|
206
|
+
// src/rust/lib.ts
|
|
207
|
+
var import_fs2 = require("fs");
|
|
208
|
+
var import_path2 = require("path");
|
|
209
|
+
var { platform, arch } = process;
|
|
210
|
+
var rustPath = `rust/${platform}-${arch}.node`;
|
|
211
|
+
var nativeSearchPaths = [
|
|
212
|
+
(0, import_path2.join)(__dirname, `../../${rustPath}`),
|
|
213
|
+
// process start from src
|
|
214
|
+
(0, import_path2.join)(__dirname, `../${rustPath}`)
|
|
215
|
+
// process start from dist
|
|
216
|
+
];
|
|
217
|
+
var mod = require(nativeSearchPaths.find(import_fs2.existsSync));
|
|
218
|
+
var FixedBufferWriter = mod.FixedBufferWriter;
|
|
219
|
+
|
|
220
|
+
// src/base/frame_sync.ts
|
|
221
|
+
var FRAME_SYNC_MARKER_WIDTH = 32;
|
|
222
|
+
function buildWrapperHTML(targetURL, size) {
|
|
223
|
+
const { width, height } = size;
|
|
224
|
+
return `<!DOCTYPE html>
|
|
225
|
+
<html>
|
|
226
|
+
<head>
|
|
227
|
+
<meta charset="UTF-8">
|
|
228
|
+
<style>
|
|
229
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
230
|
+
html, body { width: ${width}px; height: ${height + 1}px; overflow: hidden; }
|
|
231
|
+
#target {
|
|
232
|
+
position: absolute;
|
|
233
|
+
top: 0;
|
|
234
|
+
left: 0;
|
|
235
|
+
width: ${width}px;
|
|
236
|
+
height: ${height}px;
|
|
237
|
+
border: none;
|
|
238
|
+
display: block;
|
|
239
|
+
}
|
|
240
|
+
#stego {
|
|
241
|
+
position: absolute;
|
|
242
|
+
top: ${height}px;
|
|
243
|
+
left: 0;
|
|
244
|
+
width: ${width}px;
|
|
245
|
+
height: 1px;
|
|
246
|
+
display: block;
|
|
247
|
+
image-rendering: pixelated;
|
|
248
|
+
}
|
|
249
|
+
</style>
|
|
250
|
+
</head>
|
|
251
|
+
<body>
|
|
252
|
+
<iframe id="target" src="${targetURL}"></iframe>
|
|
253
|
+
<canvas id="stego" width="${width}" height="1"></canvas>
|
|
254
|
+
<script>
|
|
255
|
+
(function() {
|
|
256
|
+
const WIDTH = ${width};
|
|
257
|
+
const MARKER_WIDTH = ${FRAME_SYNC_MARKER_WIDTH};
|
|
258
|
+
const canvas = document.getElementById('stego');
|
|
259
|
+
const ctx = canvas.getContext('2d', { willReadFrequently: true });
|
|
260
|
+
let startTime = null;
|
|
261
|
+
let rafId = null;
|
|
262
|
+
|
|
263
|
+
function encodeTimestamp(timestampMs) {
|
|
264
|
+
const imageData = ctx.createImageData(WIDTH, 1);
|
|
265
|
+
const data = imageData.data;
|
|
266
|
+
|
|
267
|
+
const timestampInt = Math.floor(timestampMs) >>> 0;
|
|
268
|
+
|
|
269
|
+
for (let i = 0; i < MARKER_WIDTH; i++) {
|
|
270
|
+
const bit = (timestampInt >>> (MARKER_WIDTH - 1 - i)) & 1;
|
|
271
|
+
const value = bit ? 255 : 0;
|
|
272
|
+
const idx = i * 4;
|
|
273
|
+
data[idx] = value;
|
|
274
|
+
data[idx + 1] = value;
|
|
275
|
+
data[idx + 2] = value;
|
|
276
|
+
data[idx + 3] = 255;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
for (let i = MARKER_WIDTH; i < WIDTH; i++) {
|
|
280
|
+
const idx = i * 4;
|
|
281
|
+
data[idx] = 0;
|
|
282
|
+
data[idx + 1] = 0;
|
|
283
|
+
data[idx + 2] = 0;
|
|
284
|
+
data[idx + 3] = 255;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
ctx.putImageData(imageData, 0, 0);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function updateLoop() {
|
|
291
|
+
if (startTime === null) return;
|
|
292
|
+
const elapsed = performance.now() - startTime;
|
|
293
|
+
encodeTimestamp(elapsed);
|
|
294
|
+
rafId = requestAnimationFrame(updateLoop);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
window.__pup_start_recording__ = () => {
|
|
298
|
+
startTime = performance.now();
|
|
299
|
+
encodeTimestamp(0);
|
|
300
|
+
requestAnimationFrame(updateLoop);
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
window.__pup_stop_recording__ = () => {
|
|
304
|
+
if (rafId !== null) {
|
|
305
|
+
cancelAnimationFrame(rafId);
|
|
306
|
+
rafId = null;
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
})();
|
|
310
|
+
</script>
|
|
311
|
+
</body>
|
|
312
|
+
</html>`;
|
|
313
|
+
}
|
|
314
|
+
function decodeTimestamp(bitmap, size) {
|
|
315
|
+
const { width, height } = size;
|
|
316
|
+
if (width < FRAME_SYNC_MARKER_WIDTH || height < 2) {
|
|
317
|
+
return void 0;
|
|
318
|
+
}
|
|
319
|
+
const markerRow = height - 1;
|
|
320
|
+
let timestamp = 0;
|
|
321
|
+
for (let i = 0; i < FRAME_SYNC_MARKER_WIDTH; i++) {
|
|
322
|
+
const pixelIdx = (markerRow * width + i) * 4;
|
|
323
|
+
const r = bitmap[pixelIdx] ?? 0;
|
|
324
|
+
const bit = r > 127 ? 1 : 0;
|
|
325
|
+
timestamp = timestamp << 1 | bit;
|
|
326
|
+
}
|
|
327
|
+
timestamp = timestamp >>> 0;
|
|
328
|
+
if (!Number.isFinite(timestamp) || timestamp < 0 || timestamp > 1e7) {
|
|
329
|
+
return void 0;
|
|
330
|
+
}
|
|
331
|
+
return timestamp;
|
|
332
|
+
}
|
|
333
|
+
function startSync(cdp) {
|
|
334
|
+
return cdp.sendCommand("Runtime.evaluate", {
|
|
335
|
+
expression: `window.__pup_start_recording__()`
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
function stopSync(cdp) {
|
|
339
|
+
return cdp.sendCommand("Runtime.evaluate", {
|
|
340
|
+
expression: `window.__pup_stop_recording__()`
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// src/base/image.ts
|
|
345
|
+
function isEmpty(image) {
|
|
346
|
+
const size = image.getSize();
|
|
347
|
+
if (size.width === 0 || size.height === 0) return true;
|
|
348
|
+
return image.isEmpty();
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// src/base/proxy.ts
|
|
352
|
+
var import_electron2 = require("electron");
|
|
353
|
+
var TAG = "[Proxy]";
|
|
354
|
+
function proxiedUrl(url) {
|
|
355
|
+
if (!url.startsWith("http")) {
|
|
356
|
+
return url;
|
|
357
|
+
}
|
|
358
|
+
const match = url.match(/^https:\/\/([^-]+)-boss\.hdslb\.com(.*)$/);
|
|
359
|
+
if (match) {
|
|
360
|
+
const [, prefix, path] = match;
|
|
361
|
+
return `http://${prefix}-boss.bilibili.co${path}`;
|
|
362
|
+
}
|
|
363
|
+
return url;
|
|
364
|
+
}
|
|
365
|
+
function enableProxy() {
|
|
366
|
+
import_electron2.session.defaultSession.webRequest.onBeforeRequest((details, callback) => {
|
|
367
|
+
const url = details.url;
|
|
368
|
+
const proxied = proxiedUrl(url);
|
|
369
|
+
if (proxied === url) {
|
|
370
|
+
return callback({ cancel: false });
|
|
371
|
+
} else {
|
|
372
|
+
logger.debug(TAG, `${url} -> ${proxied}`);
|
|
373
|
+
callback({ cancel: false, redirectURL: proxied });
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// src/base/retry.ts
|
|
379
|
+
var import_promises = require("timers/promises");
|
|
380
|
+
|
|
381
|
+
// src/base/timing.ts
|
|
382
|
+
function sleep(ms) {
|
|
383
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// src/base/retry.ts
|
|
387
|
+
function useRetry({
|
|
388
|
+
fn,
|
|
389
|
+
maxAttempts = 3,
|
|
390
|
+
timeout
|
|
391
|
+
}) {
|
|
392
|
+
const timeoutError = new Error(`timeout over ${timeout}ms`);
|
|
393
|
+
return async function(...args) {
|
|
394
|
+
let attempt = 0;
|
|
395
|
+
while (true) {
|
|
396
|
+
try {
|
|
397
|
+
const promises = [fn(...args)];
|
|
398
|
+
if (timeout) {
|
|
399
|
+
promises.push(
|
|
400
|
+
(0, import_promises.setTimeout)(timeout).then(() => {
|
|
401
|
+
throw timeoutError;
|
|
402
|
+
})
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
return await Promise.race(promises);
|
|
406
|
+
} catch (e) {
|
|
407
|
+
attempt++;
|
|
408
|
+
if (attempt >= maxAttempts) {
|
|
409
|
+
throw e;
|
|
410
|
+
}
|
|
411
|
+
await sleep(Math.pow(2, attempt) * 100 + Math.random() * 100);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// src/base/record.ts
|
|
418
|
+
var TAG2 = "[Record]";
|
|
419
|
+
async function loadWindow(source, options) {
|
|
420
|
+
if (!source.startsWith("file://") && !source.match(/^https?:\/\//)) {
|
|
421
|
+
throw new Error("invalid source");
|
|
422
|
+
}
|
|
423
|
+
const { width, height, useInnerProxy } = options;
|
|
424
|
+
import_electron3.session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
|
|
425
|
+
const responseHeaders = { ...details.responseHeaders };
|
|
426
|
+
delete responseHeaders["x-frame-options"];
|
|
427
|
+
delete responseHeaders["X-Frame-Options"];
|
|
428
|
+
delete responseHeaders["content-security-policy"];
|
|
429
|
+
delete responseHeaders["Content-Security-Policy"];
|
|
430
|
+
callback({ cancel: false, responseHeaders });
|
|
431
|
+
});
|
|
432
|
+
let src = source;
|
|
433
|
+
if (useInnerProxy) {
|
|
434
|
+
src = proxiedUrl(source);
|
|
435
|
+
enableProxy();
|
|
436
|
+
}
|
|
437
|
+
const win = new import_electron3.BrowserWindow({
|
|
438
|
+
width,
|
|
439
|
+
height: height + 1,
|
|
440
|
+
show: false,
|
|
441
|
+
transparent: true,
|
|
442
|
+
backgroundColor: void 0,
|
|
443
|
+
webPreferences: {
|
|
444
|
+
offscreen: true,
|
|
445
|
+
backgroundThrottling: false,
|
|
446
|
+
nodeIntegration: true,
|
|
447
|
+
contextIsolation: false,
|
|
448
|
+
webSecurity: false,
|
|
449
|
+
allowRunningInsecureContent: true,
|
|
450
|
+
experimentalFeatures: true
|
|
451
|
+
}
|
|
452
|
+
});
|
|
453
|
+
win.webContents.on("console-message", (event) => {
|
|
454
|
+
if (event.level === "error") {
|
|
455
|
+
logger.error(TAG2, "console:", event.message);
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
const wrapperHTML = buildWrapperHTML(src, { width, height });
|
|
459
|
+
const dataURL = `data:text/html;charset=utf-8,${encodeURIComponent(wrapperHTML)}`;
|
|
460
|
+
let token;
|
|
461
|
+
await new Promise((resolve2, reject) => {
|
|
462
|
+
token = setTimeout(() => {
|
|
463
|
+
reject(new Error("load window timeout"));
|
|
464
|
+
}, 20 * 1e3);
|
|
465
|
+
win.webContents.once("did-finish-load", resolve2);
|
|
466
|
+
win.webContents.once("did-fail-load", (_event, code, desc, url) => {
|
|
467
|
+
reject(new Error(`failed to load ${url}: [${code}] ${desc}`));
|
|
468
|
+
});
|
|
469
|
+
win.webContents.once("render-process-gone", (_event, details) => {
|
|
470
|
+
const { exitCode, reason } = details;
|
|
471
|
+
reject(new Error(`renderer crashed: ${exitCode}, ${reason}`));
|
|
472
|
+
});
|
|
473
|
+
win.loadURL(dataURL);
|
|
474
|
+
});
|
|
475
|
+
clearTimeout(token);
|
|
476
|
+
return win;
|
|
477
|
+
}
|
|
478
|
+
async function record(source, options) {
|
|
479
|
+
logger.info(TAG2, `progress: 0%`);
|
|
480
|
+
const { outDir, fps, width, height, duration } = options;
|
|
481
|
+
const win = await useRetry({ fn: loadWindow, maxAttempts: 2 })(
|
|
482
|
+
source,
|
|
483
|
+
options
|
|
484
|
+
);
|
|
485
|
+
await (0, import_promises2.mkdir)(outDir, { recursive: true });
|
|
486
|
+
const cdp = win.webContents.debugger;
|
|
487
|
+
cdp.attach("1.3");
|
|
488
|
+
win.webContents.setFrameRate(fps);
|
|
489
|
+
if (!win.webContents.isPainting()) {
|
|
490
|
+
win.webContents.startPainting();
|
|
491
|
+
}
|
|
492
|
+
const bgraPath = (0, import_path3.join)(outDir, "output.bgra");
|
|
493
|
+
const total = Math.ceil(fps * duration);
|
|
494
|
+
const frameInterval = 1e3 / fps;
|
|
495
|
+
const bufferSize = width * height * 4;
|
|
496
|
+
const writer = new FixedBufferWriter(bgraPath, bufferSize, fps);
|
|
497
|
+
let written = 0;
|
|
498
|
+
let lastWrittenTime;
|
|
499
|
+
let progress = 0;
|
|
500
|
+
let frameError;
|
|
501
|
+
let resolver;
|
|
502
|
+
let rejecter;
|
|
503
|
+
const scheduleWrite = (buffer) => {
|
|
504
|
+
written++;
|
|
505
|
+
try {
|
|
506
|
+
writer.write(buffer);
|
|
507
|
+
} catch (error) {
|
|
508
|
+
frameError ??= error;
|
|
509
|
+
}
|
|
510
|
+
};
|
|
511
|
+
const paint = (_e, _r, image) => {
|
|
512
|
+
if (frameError) {
|
|
513
|
+
rejecter?.(frameError);
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
if (written >= total) {
|
|
517
|
+
resolver?.();
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
if (isEmpty(image)) return;
|
|
521
|
+
const bitmap = image.toBitmap();
|
|
522
|
+
const currentTime = decodeTimestamp(bitmap, image.getSize());
|
|
523
|
+
if (currentTime === void 0) {
|
|
524
|
+
frameError ??= new Error(`no timestamp @ ${written}`);
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
const bytesPerRow = width * 4;
|
|
528
|
+
const cropped = bitmap.subarray(0, height * bytesPerRow);
|
|
529
|
+
if (lastWrittenTime === void 0) {
|
|
530
|
+
scheduleWrite(cropped);
|
|
531
|
+
lastWrittenTime = currentTime;
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
const timeSinceLastFrame = currentTime - lastWrittenTime;
|
|
535
|
+
if (timeSinceLastFrame < frameInterval * 0.8) {
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
if (timeSinceLastFrame <= frameInterval * 1.2) {
|
|
539
|
+
scheduleWrite(cropped);
|
|
540
|
+
} else {
|
|
541
|
+
const framesToInsert = Math.round(timeSinceLastFrame / frameInterval);
|
|
542
|
+
for (let i = 0; i < framesToInsert && written < total; i++) {
|
|
543
|
+
scheduleWrite(cropped);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
lastWrittenTime = currentTime;
|
|
547
|
+
const newProgress = Math.floor(written / total * 100);
|
|
548
|
+
if (Math.abs(newProgress - progress) > 10) {
|
|
549
|
+
progress = newProgress;
|
|
550
|
+
logger.info(TAG2, `progress: ${Math.round(progress)}%`);
|
|
551
|
+
}
|
|
552
|
+
};
|
|
553
|
+
win.webContents.on("paint", paint);
|
|
554
|
+
await startSync(cdp);
|
|
555
|
+
try {
|
|
556
|
+
await new Promise((r, j) => [resolver, rejecter] = [r, j]);
|
|
557
|
+
} finally {
|
|
558
|
+
await stopSync(cdp);
|
|
559
|
+
win.webContents.off("paint", paint);
|
|
560
|
+
await writer.close();
|
|
561
|
+
}
|
|
562
|
+
if (frameError || written === 0) {
|
|
563
|
+
throw frameError ?? new Error("no frames captured");
|
|
564
|
+
}
|
|
565
|
+
try {
|
|
566
|
+
const result = { options, written, bgraPath };
|
|
567
|
+
await (0, import_promises2.writeFile)((0, import_path3.join)(outDir, "record.json"), JSON.stringify(result));
|
|
568
|
+
logger.info(TAG2, `progress: 100%, ${written} frames written`);
|
|
569
|
+
} finally {
|
|
570
|
+
win.close();
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// src/common.ts
|
|
575
|
+
var import_commander = require("commander");
|
|
576
|
+
|
|
577
|
+
// src/base/noerr.ts
|
|
578
|
+
function noerr(fn, defaultValue) {
|
|
579
|
+
return (...args) => {
|
|
580
|
+
try {
|
|
581
|
+
const ret = fn(...args);
|
|
582
|
+
if (ret instanceof Promise) {
|
|
583
|
+
return ret.catch(() => defaultValue);
|
|
584
|
+
}
|
|
585
|
+
return ret;
|
|
586
|
+
} catch {
|
|
587
|
+
return defaultValue;
|
|
588
|
+
}
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// src/common.ts
|
|
593
|
+
var DEFAULT_WIDTH = 1920;
|
|
594
|
+
var DEFAULT_HEIGHT = 1080;
|
|
595
|
+
var DEFAULT_FPS = 30;
|
|
596
|
+
var DEFAULT_DURATION = 5;
|
|
597
|
+
var DEFAULT_OUT_DIR = "out";
|
|
598
|
+
function makeCLI(name, callback) {
|
|
599
|
+
import_commander.program.name(name).argument("<source>", "URL \u6216 HTML data").option("-w, --width <number>", "\u89C6\u9891\u5BBD\u5EA6", `${DEFAULT_WIDTH}`).option("-h, --height <number>", "\u89C6\u9891\u9AD8\u5EA6", `${DEFAULT_HEIGHT}`).option("-f, --fps <number>", "\u5E27\u7387", `${DEFAULT_FPS}`).option("-t, --duration <number>", "\u5F55\u5236\u65F6\u957F\uFF08\u79D2\uFF09", `${DEFAULT_DURATION}`).option("-o, --out-dir <path>", "\u8F93\u51FA\u76EE\u5F55", `${DEFAULT_OUT_DIR}`).option("-a, --with-alpha-channel", "\u8F93\u51FA\u5305\u542B alpha \u901A\u9053\u7684\u89C6\u9891", false).option(
|
|
600
|
+
"--use-inner-proxy",
|
|
601
|
+
"\u4F7F\u7528 B \u7AD9\u5185\u7F51\u4EE3\u7406\u52A0\u901F\u8D44\u6E90\u8BBF\u95EE",
|
|
602
|
+
pupUseInnerProxy
|
|
603
|
+
).action(async (source, opts) => {
|
|
604
|
+
try {
|
|
605
|
+
await callback(source, {
|
|
606
|
+
width: noerr(parseNumber, DEFAULT_WIDTH)(opts.width),
|
|
607
|
+
height: noerr(parseNumber, DEFAULT_HEIGHT)(opts.height),
|
|
608
|
+
fps: noerr(parseNumber, DEFAULT_FPS)(opts.fps),
|
|
609
|
+
duration: noerr(parseNumber, DEFAULT_DURATION)(opts.duration),
|
|
610
|
+
outDir: opts.outDir ?? DEFAULT_OUT_DIR,
|
|
611
|
+
withAlphaChannel: opts.withAlphaChannel ?? false,
|
|
612
|
+
useInnerProxy: opts.useInnerProxy ?? pupUseInnerProxy
|
|
613
|
+
});
|
|
614
|
+
} catch (e) {
|
|
615
|
+
logger.fatal(e);
|
|
616
|
+
}
|
|
617
|
+
});
|
|
618
|
+
import_commander.program.parse(pargs());
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// src/app.ts
|
|
622
|
+
process.once("exit", () => import_electron4.app.quit());
|
|
623
|
+
makeCLI("app", async (source, options) => {
|
|
624
|
+
try {
|
|
625
|
+
ELECTRON_OPTS.forEach((o) => import_electron4.app.commandLine.appendSwitch(o));
|
|
626
|
+
import_electron4.app.dock?.hide();
|
|
627
|
+
await import_electron4.app.whenReady();
|
|
628
|
+
await record(source, options);
|
|
629
|
+
} finally {
|
|
630
|
+
import_electron4.app.quit();
|
|
631
|
+
}
|
|
632
|
+
});
|
|
633
|
+
//# sourceMappingURL=app.cjs.map
|