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