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.
Files changed (55) hide show
  1. package/Cargo.lock +230 -0
  2. package/Cargo.toml +23 -0
  3. package/LICENSE +26 -0
  4. package/README.md +96 -0
  5. package/build.rs +5 -0
  6. package/build.ts +55 -0
  7. package/build_rust.ts +51 -0
  8. package/dist/cjs/app.cjs +633 -0
  9. package/dist/cjs/app.cjs.map +7 -0
  10. package/dist/cjs/cli.cjs +691 -0
  11. package/dist/cjs/cli.cjs.map +7 -0
  12. package/dist/cjs/index.cjs +781 -0
  13. package/dist/cjs/index.cjs.map +7 -0
  14. package/dist/cli.js +667 -0
  15. package/dist/cli.js.map +7 -0
  16. package/dist/index.d.ts +93 -0
  17. package/dist/index.js +728 -0
  18. package/dist/index.js.map +7 -0
  19. package/package.json +37 -0
  20. package/rust/darwin-arm64.node +0 -0
  21. package/rust/darwin-x64.node +0 -0
  22. package/rust/linux-arm64.node +0 -0
  23. package/rust/linux-x64.node +0 -0
  24. package/src/app.ts +19 -0
  25. package/src/base/abort.ts +75 -0
  26. package/src/base/constants.ts +18 -0
  27. package/src/base/electron.ts +51 -0
  28. package/src/base/encoder.ts +35 -0
  29. package/src/base/env.ts +21 -0
  30. package/src/base/ffmpeg.ts +188 -0
  31. package/src/base/frame_sync.ts +139 -0
  32. package/src/base/image.ts +9 -0
  33. package/src/base/lazy.ts +20 -0
  34. package/src/base/limiter.ts +58 -0
  35. package/src/base/logging.ts +123 -0
  36. package/src/base/noerr.ts +18 -0
  37. package/src/base/parser.ts +12 -0
  38. package/src/base/process.ts +35 -0
  39. package/src/base/proxy.ts +33 -0
  40. package/src/base/record.ts +228 -0
  41. package/src/base/retry.ts +40 -0
  42. package/src/base/stream.ts +74 -0
  43. package/src/base/timing.ts +23 -0
  44. package/src/base/types.ts +19 -0
  45. package/src/cli.ts +6 -0
  46. package/src/common.ts +53 -0
  47. package/src/index.ts +14 -0
  48. package/src/pup.ts +142 -0
  49. package/src/rust/lib.rs +105 -0
  50. package/src/rust/lib.ts +28 -0
  51. package/tsconfig.json +25 -0
  52. package/x265/darwin-arm64 +0 -0
  53. package/x265/darwin-x64 +0 -0
  54. package/x265/linux-arm64 +0 -0
  55. package/x265/linux-x64 +0 -0
@@ -0,0 +1,691 @@
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 = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __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 || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
22
+ mod
23
+ ));
24
+
25
+ // src/common.ts
26
+ var import_commander = require("commander");
27
+
28
+ // src/base/constants.ts
29
+ var import_fs = require("fs");
30
+ var import_path = require("path");
31
+
32
+ // src/base/env.ts
33
+ function penv(name, parser, defaultValue) {
34
+ try {
35
+ return parser(process.env[name]);
36
+ } catch {
37
+ return defaultValue;
38
+ }
39
+ }
40
+
41
+ // src/base/parser.ts
42
+ function parseNumber(value) {
43
+ if (typeof value === "number") {
44
+ return value;
45
+ }
46
+ const num = Number(value);
47
+ if (Number.isNaN(num)) {
48
+ throw new Error(`Value ${value} is not a valid number`);
49
+ }
50
+ return num;
51
+ }
52
+
53
+ // src/base/constants.ts
54
+ var pupAppSearchPaths = [
55
+ (0, import_path.resolve)(__dirname, "cjs/app.cjs"),
56
+ // process from dist
57
+ (0, import_path.resolve)(__dirname, "app.cjs"),
58
+ // process from dist/cjs
59
+ (0, import_path.resolve)(__dirname, "../../cjs/app.cjs")
60
+ // process from src
61
+ ];
62
+ var pupAppPath = pupAppSearchPaths.find(import_fs.existsSync);
63
+ var env = process.env;
64
+ var pupLogLevel = penv("PUP_LOG_LEVEL", parseNumber, 2);
65
+ var pupUseInnerProxy = env["PUP_USE_INNER_PROXY"] === "1";
66
+ var pupFFmpegPath = env["FFMPEG_BIN"] ?? `ffmpeg`;
67
+
68
+ // src/base/logging.ts
69
+ var DEBUG = "<pup@debug>";
70
+ var INFO = "<pup@info>";
71
+ var WARN = "<pup@warn>";
72
+ var ERROR = "<pup@error>";
73
+ var FATAL = "<pup@fatal>";
74
+ var Logger = class {
75
+ _impl;
76
+ get impl() {
77
+ return this._impl;
78
+ }
79
+ set impl(value) {
80
+ const debug = value.debug ?? console.debug;
81
+ const info = value.info ?? console.info;
82
+ const warn = value.warn ?? console.warn;
83
+ const error = value.error ?? console.error;
84
+ this._impl = {
85
+ debug: pupLogLevel >= 3 ? debug : void 0,
86
+ info: pupLogLevel >= 2 ? info : void 0,
87
+ warn: pupLogLevel >= 1 ? warn : void 0,
88
+ error: pupLogLevel >= 0 ? error : void 0
89
+ };
90
+ }
91
+ constructor() {
92
+ this.impl = console;
93
+ }
94
+ debug(...messages) {
95
+ this.impl?.debug?.(DEBUG, ...messages);
96
+ }
97
+ info(...messages) {
98
+ this.impl?.info?.(INFO, ...messages);
99
+ }
100
+ warn(...messages) {
101
+ this.impl?.warn?.(WARN, ...messages);
102
+ }
103
+ error(...messages) {
104
+ this.impl?.error?.(ERROR, ...messages);
105
+ }
106
+ fatal(...messages) {
107
+ this.impl?.error?.(FATAL, ...messages);
108
+ process.exit(1);
109
+ }
110
+ dispatch(message) {
111
+ if (message.startsWith(DEBUG)) {
112
+ this.debug(message.slice(DEBUG.length + 1));
113
+ } else if (message.startsWith(INFO)) {
114
+ this.info(message.slice(INFO.length + 1));
115
+ } else if (message.startsWith(WARN)) {
116
+ this.warn(message.slice(WARN.length + 1));
117
+ } else if (message.startsWith(ERROR)) {
118
+ this.error(message.slice(ERROR.length + 1));
119
+ } else {
120
+ this.info(message);
121
+ }
122
+ }
123
+ attach(proc, name) {
124
+ return new Promise((resolve3, reject) => {
125
+ this.debug(`${name}.attach`);
126
+ let fatal = "";
127
+ const dispatch = (data) => {
128
+ const message = data.toString();
129
+ if (message.startsWith(FATAL)) {
130
+ fatal += message.slice(FATAL.length + 1);
131
+ } else {
132
+ this.dispatch(message);
133
+ }
134
+ };
135
+ proc.stderr?.on("data", dispatch);
136
+ proc.stdout?.on("data", dispatch);
137
+ proc.on("message", dispatch).on("error", (err) => {
138
+ fatal += err.message;
139
+ proc.kill();
140
+ }).once("close", (code, signal) => {
141
+ if (code || signal || fatal) {
142
+ fatal ||= `command failed: ${proc.spawnargs.join(" ")}`;
143
+ this.error(`${name}.close`, { code, signal, fatal });
144
+ reject(new Error(fatal));
145
+ } else {
146
+ this.debug(`${name}.close`);
147
+ resolve3();
148
+ }
149
+ }).on("unhandledRejection", (reason) => {
150
+ this.error(`${name}.unhandled`, reason);
151
+ }).on("uncaughtExceptionMonitor", (err) => {
152
+ this.error(`${name}.unhandled`, err);
153
+ });
154
+ });
155
+ }
156
+ };
157
+ var logger = new Logger();
158
+
159
+ // src/base/noerr.ts
160
+ function noerr(fn, defaultValue) {
161
+ return (...args) => {
162
+ try {
163
+ const ret = fn(...args);
164
+ if (ret instanceof Promise) {
165
+ return ret.catch(() => defaultValue);
166
+ }
167
+ return ret;
168
+ } catch {
169
+ return defaultValue;
170
+ }
171
+ };
172
+ }
173
+
174
+ // src/base/process.ts
175
+ var import_child_process = require("child_process");
176
+ var PUP_ARGS_ENV_KEY = "__PUP_ARGS__";
177
+ function pargs() {
178
+ const pupArgs = process.env[PUP_ARGS_ENV_KEY];
179
+ if (pupArgs) {
180
+ const args = ["exec", ...process.argv.slice(-1)];
181
+ args.push(...JSON.parse(pupArgs));
182
+ logger.debug("pupargs", args);
183
+ return args;
184
+ }
185
+ logger.debug("procargv", process.argv);
186
+ return process.argv;
187
+ }
188
+ function exec(cmd, options) {
189
+ const parts = cmd.split(" ").filter((s) => s.length);
190
+ const [command, ...args] = parts;
191
+ if (!command) throw new Error("empty command");
192
+ const proc = (0, import_child_process.spawn)(command, args, {
193
+ stdio: "inherit",
194
+ ...options
195
+ });
196
+ return { process: proc, wait: logger.attach(proc, command) };
197
+ }
198
+
199
+ // src/common.ts
200
+ var DEFAULT_WIDTH = 1920;
201
+ var DEFAULT_HEIGHT = 1080;
202
+ var DEFAULT_FPS = 30;
203
+ var DEFAULT_DURATION = 5;
204
+ var DEFAULT_OUT_DIR = "out";
205
+ function makeCLI(name, callback) {
206
+ 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(
207
+ "--use-inner-proxy",
208
+ "\u4F7F\u7528 B \u7AD9\u5185\u7F51\u4EE3\u7406\u52A0\u901F\u8D44\u6E90\u8BBF\u95EE",
209
+ pupUseInnerProxy
210
+ ).action(async (source, opts) => {
211
+ try {
212
+ await callback(source, {
213
+ width: noerr(parseNumber, DEFAULT_WIDTH)(opts.width),
214
+ height: noerr(parseNumber, DEFAULT_HEIGHT)(opts.height),
215
+ fps: noerr(parseNumber, DEFAULT_FPS)(opts.fps),
216
+ duration: noerr(parseNumber, DEFAULT_DURATION)(opts.duration),
217
+ outDir: opts.outDir ?? DEFAULT_OUT_DIR,
218
+ withAlphaChannel: opts.withAlphaChannel ?? false,
219
+ useInnerProxy: opts.useInnerProxy ?? pupUseInnerProxy
220
+ });
221
+ } catch (e) {
222
+ logger.fatal(e);
223
+ }
224
+ });
225
+ import_commander.program.parse(pargs());
226
+ }
227
+
228
+ // src/pup.ts
229
+ var import_child_process2 = require("child_process");
230
+ var import_promises = require("fs/promises");
231
+ var import_path3 = require("path");
232
+
233
+ // src/base/abort.ts
234
+ var AbortLink = class _AbortLink {
235
+ constructor(query, interval = 1e3) {
236
+ this.query = query;
237
+ this.interval = interval;
238
+ if (query) {
239
+ this.tick();
240
+ }
241
+ }
242
+ _callback;
243
+ _aborted;
244
+ _stopped = false;
245
+ static start(query, interval) {
246
+ return new _AbortLink(query, interval);
247
+ }
248
+ get aborted() {
249
+ return !this._stopped && this._aborted;
250
+ }
251
+ get stopped() {
252
+ return this._stopped;
253
+ }
254
+ async onAbort(callback) {
255
+ if (this._aborted) {
256
+ await callback();
257
+ } else {
258
+ this._callback = callback;
259
+ }
260
+ }
261
+ wait(...handles) {
262
+ const abort = new Promise((_, reject) => {
263
+ this.onAbort(async () => {
264
+ handles.forEach((h) => h.process.kill());
265
+ reject(new Error("aborted"));
266
+ });
267
+ });
268
+ return Promise.race([
269
+ abort,
270
+ Promise.all(handles.map((h) => h.wait))
271
+ //
272
+ ]);
273
+ }
274
+ stop() {
275
+ this._stopped = true;
276
+ }
277
+ tick() {
278
+ setTimeout(async () => {
279
+ if (this._stopped) {
280
+ return;
281
+ }
282
+ this._aborted = await this.query?.();
283
+ if (this._stopped) {
284
+ return;
285
+ }
286
+ if (this._aborted) {
287
+ await this._callback?.();
288
+ } else {
289
+ this.tick();
290
+ }
291
+ }, this.interval);
292
+ }
293
+ };
294
+
295
+ // src/base/electron.ts
296
+ var import_electron = __toESM(require("electron"), 1);
297
+ var ELECTRON_OPTS = [
298
+ "no-sandbox",
299
+ "disable-setuid-sandbox",
300
+ "disable-gpu",
301
+ "disable-dev-shm-usage",
302
+ "disable-software-rasterizer",
303
+ "disable-web-security",
304
+ "disable-site-isolation-trials",
305
+ "disable-features=IsolateOrigins,site-per-process",
306
+ "allow-insecure-localhost",
307
+ "ignore-certificate-errors",
308
+ "disable-blink-features=AutomationControlled",
309
+ "mute-audio",
310
+ "disable-extensions",
311
+ "disable-background-networking",
312
+ "address-family=ipv4",
313
+ "disable-async-dns",
314
+ "force-device-scale-factor=1",
315
+ "trace-warnings",
316
+ "force-color-profile=srgb",
317
+ "disable-color-correct-rendering",
318
+ "log-level=3"
319
+ ];
320
+ function runElectronApp(size, app, args) {
321
+ const electronArgs = ELECTRON_OPTS.map((a) => `--${a}`);
322
+ const cmdParts = [];
323
+ if (process.platform === "linux") {
324
+ cmdParts.push(
325
+ "xvfb-run",
326
+ "--auto-servernum",
327
+ `--server-args='-screen 0 ${size.width}x${size.height}x24'`
328
+ );
329
+ }
330
+ cmdParts.push(import_electron.default, ...electronArgs, app);
331
+ return exec(cmdParts.join(" "), {
332
+ stdio: ["ignore", "pipe", "pipe"],
333
+ shell: true,
334
+ env: {
335
+ ...process.env,
336
+ ELECTRON_DISABLE_DBUS: "1",
337
+ RUST_BACKTRACE: "full",
338
+ __PUP_ARGS__: JSON.stringify(args)
339
+ }
340
+ });
341
+ }
342
+
343
+ // src/base/ffmpeg.ts
344
+ var import_fs2 = require("fs");
345
+ var import_path2 = require("path");
346
+ var import_process3 = require("process");
347
+ var quiet = ["-hide_banner", "-loglevel", "error", "-nostats"];
348
+ function resolveX265() {
349
+ const path = `x265/${import_process3.platform}-${import_process3.arch}`;
350
+ const dirs = [
351
+ (0, import_path2.resolve)(__dirname, `../../${path}`),
352
+ // process from src
353
+ (0, import_path2.resolve)(__dirname, `../${path}`)
354
+ // process from dist
355
+ ];
356
+ const found = dirs.find(import_fs2.existsSync);
357
+ if (!found) {
358
+ throw new Error("x265 not found");
359
+ }
360
+ return found;
361
+ }
362
+ function createBgraFileCommand(bgraPath, spec, files) {
363
+ const { fps, frames } = spec;
364
+ const args = [
365
+ "-y",
366
+ ...quiet,
367
+ "-f",
368
+ "rawvideo",
369
+ "-pix_fmt",
370
+ "bgra",
371
+ "-s",
372
+ `${spec.size.width}x${spec.size.height}`,
373
+ "-r",
374
+ `${fps}`,
375
+ "-i",
376
+ bgraPath,
377
+ "-frames:v",
378
+ `${Math.floor(frames)}`
379
+ ];
380
+ if (files.mp4) {
381
+ args.push(
382
+ "-colorspace",
383
+ "bt709",
384
+ "-color_primaries",
385
+ "bt709",
386
+ "-color_trc",
387
+ "bt709",
388
+ "-c:v",
389
+ "libx264",
390
+ "-preset",
391
+ "fast",
392
+ "-threads",
393
+ "0",
394
+ "-pix_fmt",
395
+ "yuv420p",
396
+ "-movflags",
397
+ "+faststart",
398
+ files.mp4
399
+ );
400
+ }
401
+ if (files.webm) {
402
+ args.push(
403
+ "-c:v",
404
+ "libvpx-vp9",
405
+ "-row-mt",
406
+ "1",
407
+ "-cpu-used",
408
+ "1",
409
+ "-threads",
410
+ "0",
411
+ "-pix_fmt",
412
+ "yuva420p",
413
+ "-auto-alt-ref",
414
+ "0",
415
+ files.webm
416
+ );
417
+ }
418
+ return { command: pupFFmpegPath, args };
419
+ }
420
+ function createBgraToMovPipeline(bgraPath, spec, mov) {
421
+ const { fps, size } = spec;
422
+ return {
423
+ raw: {
424
+ command: pupFFmpegPath,
425
+ args: [
426
+ "-y",
427
+ ...quiet,
428
+ "-f",
429
+ "rawvideo",
430
+ "-pix_fmt",
431
+ "bgra",
432
+ "-s",
433
+ `${size.width}x${size.height}`,
434
+ "-r",
435
+ `${fps}`,
436
+ "-i",
437
+ bgraPath,
438
+ "-f",
439
+ "rawvideo",
440
+ "-pix_fmt",
441
+ "yuva420p10le",
442
+ "pipe:1"
443
+ ]
444
+ },
445
+ x265: {
446
+ command: resolveX265(),
447
+ args: [
448
+ "--no-progress",
449
+ "--log-level",
450
+ "error",
451
+ "--input",
452
+ "-",
453
+ "--input-res",
454
+ `${size.width}x${size.height}`,
455
+ "--input-csp",
456
+ "i420",
457
+ "--input-depth",
458
+ "10",
459
+ "--output-depth",
460
+ "10",
461
+ "--fps",
462
+ `${fps}`,
463
+ "--alpha",
464
+ "--bframes",
465
+ "0",
466
+ "--colorprim",
467
+ "bt709",
468
+ "--transfer",
469
+ "bt709",
470
+ "--colormatrix",
471
+ "bt709",
472
+ "--crf",
473
+ "18",
474
+ "--output",
475
+ "-"
476
+ ]
477
+ },
478
+ mux: {
479
+ command: pupFFmpegPath,
480
+ args: [
481
+ "-y",
482
+ ...quiet,
483
+ "-f",
484
+ "hevc",
485
+ "-r",
486
+ `${fps}`,
487
+ "-i",
488
+ "pipe:0",
489
+ "-c:v",
490
+ "copy",
491
+ "-tag:v",
492
+ "hvc1",
493
+ "-movflags",
494
+ "+faststart",
495
+ mov
496
+ ]
497
+ }
498
+ };
499
+ }
500
+ function createCoverCommand(src, dst) {
501
+ return {
502
+ command: pupFFmpegPath,
503
+ args: ["-y", ...quiet, "-i", src, "-frames:v", "1", "-q:v", "2", dst]
504
+ };
505
+ }
506
+
507
+ // src/base/encoder.ts
508
+ function encodeBgraFile(bgraPath, outputPath, spec, format) {
509
+ const files = format === "mp4" ? { mp4: outputPath } : { webm: outputPath };
510
+ const cmd = createBgraFileCommand(bgraPath, spec, files);
511
+ return exec(`${cmd.command} ${cmd.args.join(" ")}`, {
512
+ stdio: ["ignore", "inherit", "inherit"]
513
+ });
514
+ }
515
+ function encodeBgraToMov(bgraPath, movPath, spec) {
516
+ const x265 = createBgraToMovPipeline(bgraPath, spec, movPath);
517
+ const shellCmd = [
518
+ `${x265.raw.command} ${x265.raw.args.join(" ")}`,
519
+ `${x265.x265.command} ${x265.x265.args.join(" ")}`,
520
+ `${x265.mux.command} ${x265.mux.args.join(" ")}`
521
+ ].join(" | ");
522
+ return exec(shellCmd, {
523
+ stdio: ["ignore", "inherit", "inherit"],
524
+ shell: true
525
+ });
526
+ }
527
+
528
+ // src/base/limiter.ts
529
+ var ConcurrencyLimiter = class {
530
+ constructor(maxConcurrency) {
531
+ this.maxConcurrency = maxConcurrency;
532
+ }
533
+ _active = 0;
534
+ _queue = [];
535
+ _pending = 0;
536
+ _ended = false;
537
+ get active() {
538
+ return this._active;
539
+ }
540
+ get pending() {
541
+ return this._pending;
542
+ }
543
+ async schedule(fn) {
544
+ if (this._ended) {
545
+ throw new Error("ended");
546
+ }
547
+ return new Promise((resolve3, reject) => {
548
+ const run = () => {
549
+ this._active++;
550
+ this._pending--;
551
+ fn().then(resolve3).catch(reject).finally(() => {
552
+ this._active--;
553
+ this.next();
554
+ });
555
+ };
556
+ this._pending++;
557
+ if (this._active < this.maxConcurrency) {
558
+ run();
559
+ } else {
560
+ this._queue.push(run);
561
+ }
562
+ });
563
+ }
564
+ async end() {
565
+ if (!this._ended) {
566
+ this._ended = true;
567
+ while (this._active > 0 || this._pending > 0) {
568
+ await new Promise((resolve3) => setTimeout(resolve3, 50));
569
+ }
570
+ }
571
+ }
572
+ next() {
573
+ if (this._active < this.maxConcurrency && this._queue.length > 0) {
574
+ this._queue.shift()?.();
575
+ }
576
+ }
577
+ };
578
+
579
+ // src/base/stream.ts
580
+ function waitAll(...procs) {
581
+ return Promise.all(
582
+ procs.map(
583
+ (proc) => new Promise((resolve3, reject) => {
584
+ proc.on("error", reject);
585
+ proc.on(
586
+ "close",
587
+ (code) => code === 0 ? resolve3() : reject(new Error(`exit ${code ?? "null"}`))
588
+ );
589
+ })
590
+ )
591
+ );
592
+ }
593
+
594
+ // src/pup.ts
595
+ var TAG = "[pup]";
596
+ var PROGRESS_TAG = " progress: ";
597
+ function runPupApp(source, options) {
598
+ logger.debug(TAG, `runPupApp`, source, options);
599
+ const args = [source];
600
+ if (options.width) args.push("--width", `${options.width}`);
601
+ if (options.height) args.push("--height", `${options.height}`);
602
+ if (options.fps) args.push("--fps", `${options.fps}`);
603
+ if (options.duration) args.push("--duration", `${options.duration}`);
604
+ if (options.outDir) args.push("--out-dir", options.outDir);
605
+ if (options.withAlphaChannel) args.push("--with-alpha-channel");
606
+ const w = options.width ?? DEFAULT_WIDTH;
607
+ const h = options.height ?? DEFAULT_HEIGHT;
608
+ const handle = runElectronApp({ width: w, height: h }, pupAppPath, args);
609
+ const counter = new ConcurrencyLimiter(1);
610
+ handle.process.stdout?.on("data", (data) => {
611
+ let message = data.toString().trim();
612
+ let start = message.indexOf(PROGRESS_TAG);
613
+ if (start < 0) {
614
+ return;
615
+ }
616
+ message = message.slice(start + PROGRESS_TAG.length);
617
+ const end = message.indexOf("%");
618
+ if (end < 0) {
619
+ return;
620
+ }
621
+ const progressStr = message.slice(0, end);
622
+ const progress = parseNumber(progressStr);
623
+ counter.schedule(async () => {
624
+ await options.onProgress?.(progress);
625
+ });
626
+ });
627
+ return { handle, counter };
628
+ }
629
+ async function pup(source, options) {
630
+ logger.debug(TAG, `pup`, source, options);
631
+ const link = AbortLink.start(options.cancelQuery);
632
+ const outDir = options.outDir ?? "out";
633
+ const t0 = performance.now();
634
+ const { handle, counter } = runPupApp(source, { ...options, outDir });
635
+ await link.wait(handle);
636
+ await counter.end();
637
+ logger.info(TAG, `capture cost ${Math.round(performance.now() - t0)}ms`);
638
+ const metaPath = (0, import_path3.join)(outDir, "record.json");
639
+ const meta = JSON.parse(await (0, import_promises.readFile)(metaPath, "utf-8"));
640
+ const { bgraPath, written, options: recordOptions } = meta;
641
+ const { fps, width, height, withAlphaChannel } = recordOptions;
642
+ const size = { width, height };
643
+ const outputs = {
644
+ mp4: withAlphaChannel ? void 0 : (0, import_path3.join)(outDir, "output.mp4"),
645
+ webm: withAlphaChannel ? (0, import_path3.join)(outDir, "output.webm") : void 0,
646
+ mov: withAlphaChannel ? (0, import_path3.join)(outDir, "output.mov") : void 0,
647
+ cover: (0, import_path3.join)(outDir, "cover.png")
648
+ };
649
+ try {
650
+ const t1 = performance.now();
651
+ const spec = { fps, frames: written, size };
652
+ const handles = [];
653
+ if (outputs.mp4) {
654
+ handles.push(encodeBgraFile(bgraPath, outputs.mp4, spec, "mp4"));
655
+ }
656
+ if (outputs.webm) {
657
+ handles.push(encodeBgraFile(bgraPath, outputs.webm, spec, "webm"));
658
+ }
659
+ if (outputs.mov) {
660
+ handles.push(encodeBgraToMov(bgraPath, outputs.mov, spec));
661
+ }
662
+ await link.wait(...handles);
663
+ const coverSrc = outputs.mov ?? outputs.webm ?? outputs.mp4;
664
+ if (coverSrc) {
665
+ const coverCmd = createCoverCommand(coverSrc, outputs.cover);
666
+ await waitAll(
667
+ (0, import_child_process2.spawn)(coverCmd.command, coverCmd.args, { stdio: "inherit" })
668
+ );
669
+ }
670
+ link.stop();
671
+ logger.info(TAG, `encoding cost ${Math.round(performance.now() - t1)}ms`);
672
+ await Promise.all([
673
+ (0, import_promises.rm)(bgraPath, { force: true }),
674
+ (0, import_promises.rm)(metaPath, { force: true })
675
+ ]);
676
+ return {
677
+ ...outputs,
678
+ width,
679
+ height,
680
+ fps,
681
+ duration: Math.ceil(written / fps)
682
+ };
683
+ } catch (error) {
684
+ await (0, import_promises.rm)(outDir, { recursive: true, force: true });
685
+ throw error;
686
+ }
687
+ }
688
+
689
+ // src/cli.ts
690
+ makeCLI("pup", pup);
691
+ //# sourceMappingURL=cli.cjs.map