recordable 0.1.0
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 +21 -0
- package/README.md +303 -0
- package/dist/actions.d.ts +140 -0
- package/dist/actions.d.ts.map +1 -0
- package/dist/actions.js +184 -0
- package/dist/actions.js.map +1 -0
- package/dist/audio/track.d.ts +45 -0
- package/dist/audio/track.d.ts.map +1 -0
- package/dist/audio/track.js +61 -0
- package/dist/audio/track.js.map +1 -0
- package/dist/browser/cursor.d.ts +33 -0
- package/dist/browser/cursor.d.ts.map +1 -0
- package/dist/browser/cursor.js +118 -0
- package/dist/browser/cursor.js.map +1 -0
- package/dist/browser/dom.d.ts +31 -0
- package/dist/browser/dom.d.ts.map +1 -0
- package/dist/browser/dom.js +134 -0
- package/dist/browser/dom.js.map +1 -0
- package/dist/browser/play-button.d.ts +11 -0
- package/dist/browser/play-button.d.ts.map +1 -0
- package/dist/browser/play-button.js +87 -0
- package/dist/browser/play-button.js.map +1 -0
- package/dist/browser/runtime.d.ts +66 -0
- package/dist/browser/runtime.d.ts.map +1 -0
- package/dist/browser/runtime.js +271 -0
- package/dist/browser/runtime.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +131 -0
- package/dist/cli.js.map +1 -0
- package/dist/compose/mix.d.ts +13 -0
- package/dist/compose/mix.d.ts.map +1 -0
- package/dist/compose/mix.js +50 -0
- package/dist/compose/mix.js.map +1 -0
- package/dist/compose/recordable.d.ts +149 -0
- package/dist/compose/recordable.d.ts.map +1 -0
- package/dist/compose/recordable.js +337 -0
- package/dist/compose/recordable.js.map +1 -0
- package/dist/compose/session.d.ts +38 -0
- package/dist/compose/session.d.ts.map +1 -0
- package/dist/compose/session.js +122 -0
- package/dist/compose/session.js.map +1 -0
- package/dist/config.d.ts +93 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +64 -0
- package/dist/config.js.map +1 -0
- package/dist/errors.d.ts +13 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +21 -0
- package/dist/errors.js.map +1 -0
- package/dist/ffmpeg.d.ts +8 -0
- package/dist/ffmpeg.d.ts.map +1 -0
- package/dist/ffmpeg.js +55 -0
- package/dist/ffmpeg.js.map +1 -0
- package/dist/formats/json.d.ts +12 -0
- package/dist/formats/json.d.ts.map +1 -0
- package/dist/formats/json.js +20 -0
- package/dist/formats/json.js.map +1 -0
- package/dist/formats/markdown/method.d.ts +25 -0
- package/dist/formats/markdown/method.d.ts.map +1 -0
- package/dist/formats/markdown/method.js +48 -0
- package/dist/formats/markdown/method.js.map +1 -0
- package/dist/formats/markdown/parse.d.ts +44 -0
- package/dist/formats/markdown/parse.d.ts.map +1 -0
- package/dist/formats/markdown/parse.js +143 -0
- package/dist/formats/markdown/parse.js.map +1 -0
- package/dist/fs.d.ts +9 -0
- package/dist/fs.d.ts.map +1 -0
- package/dist/fs.js +30 -0
- package/dist/fs.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +21 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +45 -0
- package/dist/logger.js.map +1 -0
- package/dist/schema.d.ts +5 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +100 -0
- package/dist/schema.js.map +1 -0
- package/dist/script.d.ts +21 -0
- package/dist/script.d.ts.map +1 -0
- package/dist/script.js +26 -0
- package/dist/script.js.map +1 -0
- package/dist/targets.d.ts +6 -0
- package/dist/targets.d.ts.map +1 -0
- package/dist/targets.js +13 -0
- package/dist/targets.js.map +1 -0
- package/dist/timing.d.ts +41 -0
- package/dist/timing.d.ts.map +1 -0
- package/dist/timing.js +149 -0
- package/dist/timing.js.map +1 -0
- package/dist/utils.d.ts +3 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +8 -0
- package/dist/utils.js.map +1 -0
- package/dist/validate.d.ts +8 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +54 -0
- package/dist/validate.js.map +1 -0
- package/dist/video/recorder.d.ts +57 -0
- package/dist/video/recorder.d.ts.map +1 -0
- package/dist/video/recorder.js +238 -0
- package/dist/video/recorder.js.map +1 -0
- package/dist/video/stitch.d.ts +15 -0
- package/dist/video/stitch.d.ts.map +1 -0
- package/dist/video/stitch.js +111 -0
- package/dist/video/stitch.js.map +1 -0
- package/dist/voiceover/alignment.d.ts +14 -0
- package/dist/voiceover/alignment.d.ts.map +1 -0
- package/dist/voiceover/alignment.js +13 -0
- package/dist/voiceover/alignment.js.map +1 -0
- package/dist/voiceover/cache.d.ts +22 -0
- package/dist/voiceover/cache.d.ts.map +1 -0
- package/dist/voiceover/cache.js +55 -0
- package/dist/voiceover/cache.js.map +1 -0
- package/dist/voiceover/compile.d.ts +35 -0
- package/dist/voiceover/compile.d.ts.map +1 -0
- package/dist/voiceover/compile.js +194 -0
- package/dist/voiceover/compile.js.map +1 -0
- package/dist/voiceover/elevenlabs.d.ts +16 -0
- package/dist/voiceover/elevenlabs.d.ts.map +1 -0
- package/dist/voiceover/elevenlabs.js +66 -0
- package/dist/voiceover/elevenlabs.js.map +1 -0
- package/dist/voiceover/index.d.ts +7 -0
- package/dist/voiceover/index.d.ts.map +1 -0
- package/dist/voiceover/index.js +8 -0
- package/dist/voiceover/index.js.map +1 -0
- package/dist/voiceover/mock.d.ts +15 -0
- package/dist/voiceover/mock.d.ts.map +1 -0
- package/dist/voiceover/mock.js +41 -0
- package/dist/voiceover/mock.js.map +1 -0
- package/dist/voiceover/types.d.ts +31 -0
- package/dist/voiceover/types.d.ts.map +1 -0
- package/dist/voiceover/types.js +10 -0
- package/dist/voiceover/types.js.map +1 -0
- package/package.json +86 -0
- package/recordable.schema.json +738 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import puppeteer from "puppeteer";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { sleep } from "../utils.js";
|
|
4
|
+
import { getOutputPath } from "../fs.js";
|
|
5
|
+
import { RecordableError } from "../errors.js";
|
|
6
|
+
import { stitch } from "../video/stitch.js";
|
|
7
|
+
import { addAudio } from "./mix.js";
|
|
8
|
+
export class Session {
|
|
9
|
+
comp;
|
|
10
|
+
browser = null;
|
|
11
|
+
outputPath = "";
|
|
12
|
+
finalised = false;
|
|
13
|
+
constructor(comp) {
|
|
14
|
+
this.comp = comp;
|
|
15
|
+
}
|
|
16
|
+
/** Execute the queued action sequence, then finalise the recording. */
|
|
17
|
+
async run() {
|
|
18
|
+
const { log, recorder, runtime } = this.comp;
|
|
19
|
+
log("Start", "recording…");
|
|
20
|
+
let ok = true;
|
|
21
|
+
this.outputPath = getOutputPath(this.comp.cfg);
|
|
22
|
+
recorder.init();
|
|
23
|
+
const cfg = this.comp.cfg;
|
|
24
|
+
try {
|
|
25
|
+
this.browser = await puppeteer.launch({
|
|
26
|
+
headless: cfg.headless,
|
|
27
|
+
args: [
|
|
28
|
+
`--window-size=${cfg.viewport.width},${cfg.viewport.height}`,
|
|
29
|
+
...cfg.launchArgs,
|
|
30
|
+
],
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
throw new RecordableError("BROWSER_LAUNCH", `Could not launch Chromium: ${err.message}. ` +
|
|
35
|
+
`In CI/containers add launchArgs: ["--no-sandbox"].`, { cause: err });
|
|
36
|
+
}
|
|
37
|
+
const page = await this.browser.newPage();
|
|
38
|
+
await page.setViewport({ ...cfg.viewport, deviceScaleFactor: 1 });
|
|
39
|
+
// A navigation wipes the in-page cursor overlay and any zoom transform.
|
|
40
|
+
// Re-inject (at the carried position) and reset zoom on every main-frame nav,
|
|
41
|
+
// so click-triggered navigations stay covered without an explicit re-inject.
|
|
42
|
+
if (cfg.cursor) {
|
|
43
|
+
page.on("framenavigated", (frame) => {
|
|
44
|
+
if (frame !== page.mainFrame())
|
|
45
|
+
return;
|
|
46
|
+
runtime.resetZoomState();
|
|
47
|
+
// Only mask the real pointer while recording — a paused / wait-for-user
|
|
48
|
+
// step is driven by hand, so the `cursor: none` overlay stays off.
|
|
49
|
+
if (!this.comp.recording)
|
|
50
|
+
return;
|
|
51
|
+
void runtime.injectCursor(page).catch(() => { });
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
// Finalise the recording and close the browser on SIGINT / SIGTERM.
|
|
55
|
+
const onSignal = () => {
|
|
56
|
+
log("Signal", "finalising recording…");
|
|
57
|
+
this._cleanup().finally(() => process.exit(0));
|
|
58
|
+
};
|
|
59
|
+
process.once("SIGINT", onSignal);
|
|
60
|
+
process.once("SIGTERM", onSignal);
|
|
61
|
+
try {
|
|
62
|
+
for (const item of this.comp.queue) {
|
|
63
|
+
// Lazily begin a segment before the first capture-worthy action while
|
|
64
|
+
// recording is intended — keeps a leading pause() from making an empty clip.
|
|
65
|
+
if (!item.control && this.comp.recording && !recorder.capturing) {
|
|
66
|
+
await recorder.begin(page);
|
|
67
|
+
}
|
|
68
|
+
await item.run(page);
|
|
69
|
+
if (this.comp.cfg.actionDelay > 0)
|
|
70
|
+
await sleep(this.comp.cfg.actionDelay);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
ok = false;
|
|
75
|
+
log.error(String(err));
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
process.off("SIGINT", onSignal);
|
|
79
|
+
process.off("SIGTERM", onSignal);
|
|
80
|
+
await this._cleanup();
|
|
81
|
+
log("Output", this.outputPath);
|
|
82
|
+
// Bookends the "Start" line; green only when the run actually succeeded.
|
|
83
|
+
if (ok)
|
|
84
|
+
log.success("Done", "recording complete");
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async _cleanup() {
|
|
88
|
+
await this._finalize();
|
|
89
|
+
await this.comp.recorder.dispose();
|
|
90
|
+
if (this.browser) {
|
|
91
|
+
await this.browser.close().catch(() => { });
|
|
92
|
+
this.browser = null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/** Seal the recording: stitch the captured/inserted segments (video layer),
|
|
96
|
+
* then mix the audio track onto them (audio layer). The video defines the
|
|
97
|
+
* length. Idempotent — a signal handler and the normal path both call it. */
|
|
98
|
+
async _finalize() {
|
|
99
|
+
if (this.finalised)
|
|
100
|
+
return;
|
|
101
|
+
this.finalised = true;
|
|
102
|
+
const { recorder, audioTrack, cfg, log } = this.comp;
|
|
103
|
+
await recorder.end();
|
|
104
|
+
const segs = recorder.segments;
|
|
105
|
+
if (segs.length === 0) {
|
|
106
|
+
log("Record", "nothing was recorded — no output written");
|
|
107
|
+
recorder.removeTmp();
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
// With audio, render the silent video to a temp file then mix onto it;
|
|
111
|
+
// otherwise stitch straight to the output.
|
|
112
|
+
const needAudio = audioTrack.length > 0;
|
|
113
|
+
const videoOut = needAudio
|
|
114
|
+
? join(recorder.tmpDir, "video.mp4")
|
|
115
|
+
: this.outputPath;
|
|
116
|
+
await stitch(segs, cfg, log, videoOut, recorder.tmpDir);
|
|
117
|
+
if (needAudio)
|
|
118
|
+
await addAudio(videoOut, audioTrack.list(), this.outputPath, log);
|
|
119
|
+
recorder.removeTmp();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/compose/session.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
/** strictObject so an unknown key (usually a typo) fails validation, matching
|
|
3
|
+
* the `additionalProperties: false` of the generated config schema. */
|
|
4
|
+
export declare const ConfigSchema: z.ZodObject<{
|
|
5
|
+
viewport: z.ZodDefault<z.ZodObject<{
|
|
6
|
+
width: z.ZodNumber;
|
|
7
|
+
height: z.ZodNumber;
|
|
8
|
+
}, z.core.$strict>>;
|
|
9
|
+
fps: z.ZodDefault<z.ZodNumber>;
|
|
10
|
+
outputDir: z.ZodDefault<z.ZodString>;
|
|
11
|
+
outputName: z.ZodDefault<z.ZodString>;
|
|
12
|
+
outputTimestamp: z.ZodDefault<z.ZodBoolean>;
|
|
13
|
+
assetsDir: z.ZodDefault<z.ZodString>;
|
|
14
|
+
headless: z.ZodDefault<z.ZodBoolean>;
|
|
15
|
+
launchArgs: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
16
|
+
typingSpeed: z.ZodDefault<z.ZodNumber>;
|
|
17
|
+
videoCrf: z.ZodDefault<z.ZodNumber>;
|
|
18
|
+
videoCodec: z.ZodDefault<z.ZodString>;
|
|
19
|
+
videoPreset: z.ZodDefault<z.ZodString>;
|
|
20
|
+
zoomDuration: z.ZodDefault<z.ZodNumber>;
|
|
21
|
+
actionDelay: z.ZodDefault<z.ZodNumber>;
|
|
22
|
+
silent: z.ZodDefault<z.ZodBoolean>;
|
|
23
|
+
autoScroll: z.ZodDefault<z.ZodBoolean>;
|
|
24
|
+
scrollMargin: z.ZodDefault<z.ZodNumber>;
|
|
25
|
+
scrollSpeed: z.ZodDefault<z.ZodNumber>;
|
|
26
|
+
scrollDuration: z.ZodDefault<z.ZodNumber>;
|
|
27
|
+
cursor: z.ZodDefault<z.ZodBoolean>;
|
|
28
|
+
visitTimeout: z.ZodDefault<z.ZodNumber>;
|
|
29
|
+
baseDir: z.ZodDefault<z.ZodString>;
|
|
30
|
+
}, z.core.$strict>;
|
|
31
|
+
/** Public config input — every field optional (defaults fill the rest). */
|
|
32
|
+
export type RecordableConfig = z.input<typeof ConfigSchema>;
|
|
33
|
+
/** The full config with every field resolved — what the running instance holds. */
|
|
34
|
+
export type ResolvedConfig = z.output<typeof ConfigSchema>;
|
|
35
|
+
/** Default values for every config option (the schema's defaults, resolved). */
|
|
36
|
+
export declare const DEFAULT_CONFIG: ResolvedConfig;
|
|
37
|
+
/**
|
|
38
|
+
* Voiceover settings, carried in Markdown frontmatter (non-secret only — the API
|
|
39
|
+
* key comes from the environment). Consumed by the optional voiceover add-on;
|
|
40
|
+
* core only needs the type to round-trip frontmatter.
|
|
41
|
+
*/
|
|
42
|
+
export interface VoiceoverConfig {
|
|
43
|
+
/** TTS backend. Omit to take `RECORDABLE_TTS_PROVIDER` (default: elevenlabs). */
|
|
44
|
+
provider?: string;
|
|
45
|
+
/** Voice to synthesize with. Omit to take `RECORDABLE_VOICE_ID`. */
|
|
46
|
+
voiceId?: string;
|
|
47
|
+
/** Omit to take `RECORDABLE_MODEL_ID`, else the provider default. */
|
|
48
|
+
modelId?: string;
|
|
49
|
+
/** Prefer `ELEVENLABS_API_KEY` in the environment over inlining a key here. */
|
|
50
|
+
apiKey?: string;
|
|
51
|
+
voiceSettings?: Record<string, number>;
|
|
52
|
+
format?: string;
|
|
53
|
+
}
|
|
54
|
+
/** Options for `Recordable.insert`. Cross-fade durations are in milliseconds. */
|
|
55
|
+
export interface InsertOptions {
|
|
56
|
+
/**
|
|
57
|
+
* Cross-fade *into* the clip over this many ms — dissolves from the preceding
|
|
58
|
+
* recorded segment, or fades up from black when the clip is first (an intro).
|
|
59
|
+
* Default: 0 (hard cut).
|
|
60
|
+
*/
|
|
61
|
+
fadeIn?: number;
|
|
62
|
+
/**
|
|
63
|
+
* Cross-fade *out of* the clip over this many ms — dissolves into the
|
|
64
|
+
* following recorded segment, or fades to black when the clip is last (an
|
|
65
|
+
* outro). Default: 0 (hard cut).
|
|
66
|
+
*/
|
|
67
|
+
fadeOut?: number;
|
|
68
|
+
}
|
|
69
|
+
/** Options for `Recordable.audio`. */
|
|
70
|
+
export interface AudioOptions {
|
|
71
|
+
/**
|
|
72
|
+
* Block the chain until the clip finishes playing (an implicit `wait` for the
|
|
73
|
+
* clip's duration), so following actions land after it. Default: true. Set
|
|
74
|
+
* false for voiceover, where the clip plays *over* interleaved actions whose
|
|
75
|
+
* timing you (or the compiler) control with explicit `wait`s.
|
|
76
|
+
*/
|
|
77
|
+
wait?: boolean;
|
|
78
|
+
/** Linear gain applied to the clip (1 = unchanged). Default: 1. */
|
|
79
|
+
volume?: number;
|
|
80
|
+
}
|
|
81
|
+
/** Options for `Recordable.waitFor`. */
|
|
82
|
+
export interface WaitForOptions {
|
|
83
|
+
/**
|
|
84
|
+
* What to wait for:
|
|
85
|
+
* - `"visible"` (default) — element is present *and* rendered
|
|
86
|
+
* - `"hidden"` — element is absent or not rendered
|
|
87
|
+
* - `"present"` — element is attached to the DOM (may be hidden)
|
|
88
|
+
*/
|
|
89
|
+
state?: "visible" | "hidden" | "present";
|
|
90
|
+
/** Timeout in ms. Default: the `visitTimeout` config value. */
|
|
91
|
+
timeout?: number;
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AAUzB;wEACwE;AACxE,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;kBAkDvB,CAAC;AAEH,2EAA2E;AAC3E,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAE5D,mFAAmF;AACnF,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,YAAY,CAAC,CAAC;AAE3D,gFAAgF;AAChF,eAAO,MAAM,cAAc,EAAE,cAAuC,CAAC;AAErE;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,iFAAiF;IACjF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oEAAoE;IACpE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qEAAqE;IACrE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+EAA+E;IAC/E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,iFAAiF;AACjF,MAAM,WAAW,aAAa;IAC5B;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,sCAAsC;AACtC,MAAM,WAAW,YAAY;IAC3B;;;;;OAKG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,mEAAmE;IACnE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wCAAwC;AACxC,MAAM,WAAW,cAAc;IAC7B;;;;;OAKG;IACH,KAAK,CAAC,EAAE,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAC;IACzC,+DAA+D;IAC/D,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
// ─── Config ──────────────────────────────────────────────────────────────────
|
|
3
|
+
//
|
|
4
|
+
// `ConfigSchema` is the single source of truth for the recording config: the
|
|
5
|
+
// public input type, the fully-resolved type, the default values, and the
|
|
6
|
+
// generated JSON Schema (`schema.ts`) are all derived from it. Each field's
|
|
7
|
+
// `.default(...)` is the default; the doc comment above it is the user-facing
|
|
8
|
+
// documentation.
|
|
9
|
+
/** strictObject so an unknown key (usually a typo) fails validation, matching
|
|
10
|
+
* the `additionalProperties: false` of the generated config schema. */
|
|
11
|
+
export const ConfigSchema = z.strictObject({
|
|
12
|
+
/** Browser viewport dimensions. Default: 1920×1080 */
|
|
13
|
+
viewport: z
|
|
14
|
+
.strictObject({ width: z.number(), height: z.number() })
|
|
15
|
+
.default({ width: 1920, height: 1080 }),
|
|
16
|
+
/** Recording frame rate. Default: 30 */
|
|
17
|
+
fps: z.number().default(30),
|
|
18
|
+
/** Output directory. Relative paths resolve against `baseDir`. Default: output */
|
|
19
|
+
outputDir: z.string().default("output"),
|
|
20
|
+
/** Base filename (without extension or timestamp). Default: recordable */
|
|
21
|
+
outputName: z.string().default("recordable"),
|
|
22
|
+
/** Prepend an ISO timestamp to the filename. Default: true */
|
|
23
|
+
outputTimestamp: z.boolean().default(true),
|
|
24
|
+
/** Where generated voiceover audio is written (voiceover documents only).
|
|
25
|
+
* Relative paths resolve against `baseDir`. Default: assets */
|
|
26
|
+
assetsDir: z.string().default("assets"),
|
|
27
|
+
/** Run without a visible browser window. Default: false */
|
|
28
|
+
headless: z.boolean().default(false),
|
|
29
|
+
/** Extra Chromium flags appended to the launch args, e.g. `["--no-sandbox"]`
|
|
30
|
+
* for CI / containers / sandboxed environments. Default: [] */
|
|
31
|
+
launchArgs: z.array(z.string()).default([]),
|
|
32
|
+
/** Typing speed in characters per second. Higher = faster. Default: 7 */
|
|
33
|
+
typingSpeed: z.number().default(7),
|
|
34
|
+
/** Constant Rate Factor — lower = better quality, larger file. Default: 18 */
|
|
35
|
+
videoCrf: z.number().default(18),
|
|
36
|
+
/** FFmpeg video codec. Default: libx264 */
|
|
37
|
+
videoCodec: z.string().default("libx264"),
|
|
38
|
+
/** FFmpeg encoding preset. Default: ultrafast */
|
|
39
|
+
videoPreset: z.string().default("ultrafast"),
|
|
40
|
+
/** Default zoom transition duration in ms. Default: 600 */
|
|
41
|
+
zoomDuration: z.number().default(600),
|
|
42
|
+
/** Automatic pause inserted between every action in ms. Default: 300 */
|
|
43
|
+
actionDelay: z.number().default(300),
|
|
44
|
+
/** Suppress all console output. Default: false */
|
|
45
|
+
silent: z.boolean().default(false),
|
|
46
|
+
/** Automatically scroll an element into view before clicking or typing. Default: true */
|
|
47
|
+
autoScroll: z.boolean().default(true),
|
|
48
|
+
/** Minimum viewport margin (px) kept above/below element when auto-scrolling. Default: 120 */
|
|
49
|
+
scrollMargin: z.number().default(120),
|
|
50
|
+
/** Auto-scroll speed in px/s — faster = snappier short scrolls. Default: 1500 */
|
|
51
|
+
scrollSpeed: z.number().default(1500),
|
|
52
|
+
/** Default `scroll` action transition duration in ms. Default: 1200 */
|
|
53
|
+
scrollDuration: z.number().default(1200),
|
|
54
|
+
/** Show an animated cursor overlay that moves to elements before interacting. Default: true */
|
|
55
|
+
cursor: z.boolean().default(true),
|
|
56
|
+
/** Timeout in ms for page navigation. Default: 30000 */
|
|
57
|
+
visitTimeout: z.number().default(30_000),
|
|
58
|
+
/** Directory that relative `visit` URLs, `outputDir`, and `assetsDir` resolve
|
|
59
|
+
* against (e.g. the script file's folder). Default: "" → resolve against cwd. */
|
|
60
|
+
baseDir: z.string().default(""),
|
|
61
|
+
});
|
|
62
|
+
/** Default values for every config option (the schema's defaults, resolved). */
|
|
63
|
+
export const DEFAULT_CONFIG = ConfigSchema.parse({});
|
|
64
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Stable, machine-readable failure categories. */
|
|
2
|
+
export type ErrorCode = "CONFIG_INVALID" | "FILE_NOT_FOUND" | "FFMPEG_FAILED" | "TTS_FAILED" | "BROWSER_LAUNCH";
|
|
3
|
+
/** An expected, user-facing failure. `message` should say what went wrong *and*
|
|
4
|
+
* how to fix it; `cause` keeps the original error for debugging. */
|
|
5
|
+
export declare class RecordableError extends Error {
|
|
6
|
+
readonly code: ErrorCode;
|
|
7
|
+
constructor(code: ErrorCode, message: string, options?: {
|
|
8
|
+
cause?: unknown;
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
/** True for our own expected failures — the CLI prints these without a stack. */
|
|
12
|
+
export declare function isRecordableError(err: unknown): err is RecordableError;
|
|
13
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAOA,mDAAmD;AACnD,MAAM,MAAM,SAAS,GACjB,gBAAgB,GAChB,gBAAgB,GAChB,eAAe,GACf,YAAY,GACZ,gBAAgB,CAAC;AAErB;qEACqE;AACrE,qBAAa,eAAgB,SAAQ,KAAK;IAEtC,QAAQ,CAAC,IAAI,EAAE,SAAS;gBAAf,IAAI,EAAE,SAAS,EACxB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAO;CAKpC;AAED,iFAAiF;AACjF,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,eAAe,CAEtE"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// ─── Errors ──────────────────────────────────────────────────────────────────
|
|
2
|
+
//
|
|
3
|
+
// One error type for every *expected* failure — bad config, a missing file, an
|
|
4
|
+
// ffmpeg/TTS/browser failure. Carrying a `code` lets the CLI print a clean,
|
|
5
|
+
// actionable line (no stack trace) and lets callers branch on the cause. Genuine
|
|
6
|
+
// bugs stay plain `Error`s so their stack still surfaces.
|
|
7
|
+
/** An expected, user-facing failure. `message` should say what went wrong *and*
|
|
8
|
+
* how to fix it; `cause` keeps the original error for debugging. */
|
|
9
|
+
export class RecordableError extends Error {
|
|
10
|
+
code;
|
|
11
|
+
constructor(code, message, options = {}) {
|
|
12
|
+
super(message, options);
|
|
13
|
+
this.code = code;
|
|
14
|
+
this.name = "RecordableError";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/** True for our own expected failures — the CLI prints these without a stack. */
|
|
18
|
+
export function isRecordableError(err) {
|
|
19
|
+
return err instanceof RecordableError;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,EAAE;AACF,+EAA+E;AAC/E,4EAA4E;AAC5E,iFAAiF;AACjF,0DAA0D;AAU1D;qEACqE;AACrE,MAAM,OAAO,eAAgB,SAAQ,KAAK;IAE7B;IADX,YACW,IAAe,EACxB,OAAe,EACf,UAA+B,EAAE;QAEjC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAJf,SAAI,GAAJ,IAAI,CAAW;QAKxB,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;IAChC,CAAC;CACF;AAED,iFAAiF;AACjF,MAAM,UAAU,iBAAiB,CAAC,GAAY;IAC5C,OAAO,GAAG,YAAY,eAAe,CAAC;AACxC,CAAC"}
|
package/dist/ffmpeg.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Probe a clip's duration in seconds by parsing ffmpeg's stderr banner. 0 if unreadable. */
|
|
2
|
+
export declare function getDuration(path: string): Promise<number>;
|
|
3
|
+
export declare const FFMPEG_PATH: string;
|
|
4
|
+
/** Run ffmpeg to completion. Resolves on exit code 0; on a non-zero exit or a
|
|
5
|
+
* spawn failure rejects with a {@link RecordableError} carrying the stderr tail,
|
|
6
|
+
* so the real cause (bad filter, missing codec, unreadable input) is visible. */
|
|
7
|
+
export declare function runFfmpeg(args: string[]): Promise<void>;
|
|
8
|
+
//# sourceMappingURL=ffmpeg.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ffmpeg.d.ts","sourceRoot":"","sources":["../src/ffmpeg.ts"],"names":[],"mappings":"AAIA,6FAA6F;AAC7F,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAazD;AAYD,eAAO,MAAM,WAAW,QAAsB,CAAC;AAS/C;;kFAEkF;AAClF,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAuBvD"}
|
package/dist/ffmpeg.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { RecordableError } from "./errors.js";
|
|
4
|
+
/** Probe a clip's duration in seconds by parsing ffmpeg's stderr banner. 0 if unreadable. */
|
|
5
|
+
export function getDuration(path) {
|
|
6
|
+
return new Promise((resolve) => {
|
|
7
|
+
const proc = spawn(FFMPEG_PATH, ["-i", path], {
|
|
8
|
+
stdio: ["ignore", "ignore", "pipe"],
|
|
9
|
+
});
|
|
10
|
+
let err = "";
|
|
11
|
+
proc.stderr?.on("data", (d) => (err += String(d)));
|
|
12
|
+
proc.on("error", () => resolve(0));
|
|
13
|
+
proc.on("close", () => {
|
|
14
|
+
const m = err.match(/Duration:\s*(\d+):(\d+):(\d+(?:\.\d+)?)/);
|
|
15
|
+
resolve(m ? +m[1] * 3600 + +m[2] * 60 + parseFloat(m[3]) : 0);
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
// Prefer the ffmpeg binary bundled by @ffmpeg-installer/ffmpeg; fall back to a
|
|
20
|
+
// system `ffmpeg` on PATH if it isn't present for some reason.
|
|
21
|
+
function resolveFfmpegPath() {
|
|
22
|
+
try {
|
|
23
|
+
return createRequire(import.meta.url)("@ffmpeg-installer/ffmpeg").path;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return "ffmpeg";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export const FFMPEG_PATH = resolveFfmpegPath();
|
|
30
|
+
/** Tail of an ffmpeg stderr stream — the last `max` non-blank lines, where the
|
|
31
|
+
* actual error usually lives (the rest is the version/config banner). */
|
|
32
|
+
function tail(text, max = 8) {
|
|
33
|
+
const lines = text.split(/\r?\n/).filter((l) => l.trim());
|
|
34
|
+
return lines.slice(-max).join("\n");
|
|
35
|
+
}
|
|
36
|
+
/** Run ffmpeg to completion. Resolves on exit code 0; on a non-zero exit or a
|
|
37
|
+
* spawn failure rejects with a {@link RecordableError} carrying the stderr tail,
|
|
38
|
+
* so the real cause (bad filter, missing codec, unreadable input) is visible. */
|
|
39
|
+
export function runFfmpeg(args) {
|
|
40
|
+
return new Promise((resolve, reject) => {
|
|
41
|
+
const proc = spawn(FFMPEG_PATH, args, { stdio: ["ignore", "ignore", "pipe"] });
|
|
42
|
+
let err = "";
|
|
43
|
+
proc.stderr?.on("data", (d) => (err += String(d)));
|
|
44
|
+
proc.on("error", (e) => reject(new RecordableError("FFMPEG_FAILED", `Could not run ffmpeg: ${e.message}`, {
|
|
45
|
+
cause: e,
|
|
46
|
+
})));
|
|
47
|
+
proc.on("close", (code) => {
|
|
48
|
+
if (code === 0)
|
|
49
|
+
return resolve();
|
|
50
|
+
const detail = tail(err);
|
|
51
|
+
reject(new RecordableError("FFMPEG_FAILED", `ffmpeg exited with code ${code}${detail ? `:\n${detail}` : ""}`));
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=ffmpeg.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ffmpeg.js","sourceRoot":"","sources":["../src/ffmpeg.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C,6FAA6F;AAC7F,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,IAAI,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE;YAC5C,KAAK,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC;SACpC,CAAC,CAAC;QACH,IAAI,GAAG,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACnD,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;QACnC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YACpB,MAAM,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,yCAAyC,CAAC,CAAC;YAC/D,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAChE,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,+EAA+E;AAC/E,+DAA+D;AAC/D,SAAS,iBAAiB;IACxB,IAAI,CAAC;QACH,OAAO,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,0BAA0B,CAAC,CAAC,IAAI,CAAC;IACzE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,QAAQ,CAAC;IAClB,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,WAAW,GAAG,iBAAiB,EAAE,CAAC;AAE/C;0EAC0E;AAC1E,SAAS,IAAI,CAAC,IAAY,EAAE,GAAG,GAAG,CAAC;IACjC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAC1D,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACtC,CAAC;AAED;;kFAEkF;AAClF,MAAM,UAAU,SAAS,CAAC,IAAc;IACtC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,IAAI,GAAG,KAAK,CAAC,WAAW,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;QAC/E,IAAI,GAAG,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACnD,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE,CACrB,MAAM,CACJ,IAAI,eAAe,CAAC,eAAe,EAAE,yBAAyB,CAAC,CAAC,OAAO,EAAE,EAAE;YACzE,KAAK,EAAE,CAAC;SACT,CAAC,CACH,CACF,CAAC;QACF,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;YACxB,IAAI,IAAI,KAAK,CAAC;gBAAE,OAAO,OAAO,EAAE,CAAC;YACjC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;YACzB,MAAM,CACJ,IAAI,eAAe,CACjB,eAAe,EACf,2BAA2B,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CACjE,CACF,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Recordable } from "../compose/recordable.js";
|
|
2
|
+
import type { RecordableConfig } from "../config.js";
|
|
3
|
+
import type { Script } from "../script.js";
|
|
4
|
+
/**
|
|
5
|
+
* Build a {@link Recordable} from a JSON script without running it — a thin
|
|
6
|
+
* wrapper over `new Recordable(configOverride).fromJSON(script)`. `configOverride`
|
|
7
|
+
* (the explicit/programmatic config) wins over the script's own `config`.
|
|
8
|
+
*/
|
|
9
|
+
export declare function fromJSON(script: Script | string, configOverride?: RecordableConfig): Recordable;
|
|
10
|
+
/** Build a {@link Recordable} from a JSON script and run it to completion. */
|
|
11
|
+
export declare function runScript(script: Script | string, configOverride?: RecordableConfig): Promise<void>;
|
|
12
|
+
//# sourceMappingURL=json.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../../src/formats/json.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAS3C;;;;GAIG;AACH,wBAAgB,QAAQ,CACtB,MAAM,EAAE,MAAM,GAAG,MAAM,EACvB,cAAc,GAAE,gBAAqB,GACpC,UAAU,CAEZ;AAED,8EAA8E;AAC9E,wBAAgB,SAAS,CACvB,MAAM,EAAE,MAAM,GAAG,MAAM,EACvB,cAAc,CAAC,EAAE,gBAAgB,GAChC,OAAO,CAAC,IAAI,CAAC,CAEf"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Recordable } from "../compose/recordable.js";
|
|
2
|
+
// ─── JSON authoring format ───────────────────────────────────────────────────
|
|
3
|
+
//
|
|
4
|
+
// The declarative JSON entry points: turn a `Script` (a bare action array, a
|
|
5
|
+
// `{ config, actions }` object, or a raw JSON string) into a Recordable. The
|
|
6
|
+
// shared action model — the ACTIONS manifest, validation, and call→action
|
|
7
|
+
// mapping — lives in `../actions.ts`; this file is just the JSON doorway.
|
|
8
|
+
/**
|
|
9
|
+
* Build a {@link Recordable} from a JSON script without running it — a thin
|
|
10
|
+
* wrapper over `new Recordable(configOverride).fromJSON(script)`. `configOverride`
|
|
11
|
+
* (the explicit/programmatic config) wins over the script's own `config`.
|
|
12
|
+
*/
|
|
13
|
+
export function fromJSON(script, configOverride = {}) {
|
|
14
|
+
return new Recordable(configOverride).fromJSON(script);
|
|
15
|
+
}
|
|
16
|
+
/** Build a {@link Recordable} from a JSON script and run it to completion. */
|
|
17
|
+
export function runScript(script, configOverride) {
|
|
18
|
+
return fromJSON(script, configOverride).run();
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=json.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json.js","sourceRoot":"","sources":["../../src/formats/json.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAItD,gFAAgF;AAChF,EAAE;AACF,6EAA6E;AAC7E,6EAA6E;AAC7E,0EAA0E;AAC1E,0EAA0E;AAE1E;;;;GAIG;AACH,MAAM,UAAU,QAAQ,CACtB,MAAuB,EACvB,iBAAmC,EAAE;IAErC,OAAO,IAAI,UAAU,CAAC,cAAc,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AACzD,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,SAAS,CACvB,MAAuB,EACvB,cAAiC;IAEjC,OAAO,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,GAAG,EAAE,CAAC;AAChD,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** One parsed method call: the method name and its evaluated arguments. */
|
|
2
|
+
export interface MethodCall {
|
|
3
|
+
name: string;
|
|
4
|
+
args: unknown[];
|
|
5
|
+
}
|
|
6
|
+
/** True if a string looks like a method call — `ident(` at the start. */
|
|
7
|
+
export declare function isMethodCall(src: string): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Parse a string holding exactly one method call (`name(args)`). The name leads,
|
|
10
|
+
* its argument list is delimited by the first `(` and the trailing `)`, and the
|
|
11
|
+
* arguments are parsed as JSON5. Throws if the string is not a single call.
|
|
12
|
+
*/
|
|
13
|
+
export declare function parseMethodCall(src: string): MethodCall;
|
|
14
|
+
/**
|
|
15
|
+
* Parse a fenced code block: one method call per non-blank line, in order. Each
|
|
16
|
+
* line is an independent call (no line may carry two), which is what makes
|
|
17
|
+
* splitting on newlines sufficient.
|
|
18
|
+
*/
|
|
19
|
+
export declare function parseMethodCalls(src: string): MethodCall[];
|
|
20
|
+
/**
|
|
21
|
+
* Parse the comma-separated argument list inside a call's parens. The list is a
|
|
22
|
+
* JS array literal minus its brackets, so we wrap and parse it as JSON5.
|
|
23
|
+
*/
|
|
24
|
+
export declare function parseArgList(src: string): unknown[];
|
|
25
|
+
//# sourceMappingURL=method.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"method.d.ts","sourceRoot":"","sources":["../../../src/formats/markdown/method.ts"],"names":[],"mappings":"AAgBA,2EAA2E;AAC3E,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,EAAE,CAAC;CACjB;AAED,yEAAyE;AACzE,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAEjD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAUvD;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,EAAE,CAM1D;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,EAAE,CAUnD"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import JSON5 from "json5";
|
|
2
|
+
/** True if a string looks like a method call — `ident(` at the start. */
|
|
3
|
+
export function isMethodCall(src) {
|
|
4
|
+
return /^\s*[A-Za-z_]\w*\s*\(/.test(src);
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Parse a string holding exactly one method call (`name(args)`). The name leads,
|
|
8
|
+
* its argument list is delimited by the first `(` and the trailing `)`, and the
|
|
9
|
+
* arguments are parsed as JSON5. Throws if the string is not a single call.
|
|
10
|
+
*/
|
|
11
|
+
export function parseMethodCall(src) {
|
|
12
|
+
const s = src.trim();
|
|
13
|
+
const head = /^([A-Za-z_]\w*)\s*\(/.exec(s);
|
|
14
|
+
if (!head)
|
|
15
|
+
throw new Error(`Not a method call: ${src.trim()}`);
|
|
16
|
+
if (!s.endsWith(")"))
|
|
17
|
+
throw new Error(`A method call must end with ")": ${src.trim()}`);
|
|
18
|
+
const open = head[0].length - 1; // index of the first "("
|
|
19
|
+
const close = s.length - 1; // index of the trailing ")"
|
|
20
|
+
return { name: head[1], args: parseArgList(s.slice(open + 1, close)) };
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Parse a fenced code block: one method call per non-blank line, in order. Each
|
|
24
|
+
* line is an independent call (no line may carry two), which is what makes
|
|
25
|
+
* splitting on newlines sufficient.
|
|
26
|
+
*/
|
|
27
|
+
export function parseMethodCalls(src) {
|
|
28
|
+
return src
|
|
29
|
+
.split("\n")
|
|
30
|
+
.map((line) => line.trim())
|
|
31
|
+
.filter((line) => line !== "")
|
|
32
|
+
.map(parseMethodCall);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Parse the comma-separated argument list inside a call's parens. The list is a
|
|
36
|
+
* JS array literal minus its brackets, so we wrap and parse it as JSON5.
|
|
37
|
+
*/
|
|
38
|
+
export function parseArgList(src) {
|
|
39
|
+
if (src.trim() === "")
|
|
40
|
+
return [];
|
|
41
|
+
try {
|
|
42
|
+
return JSON5.parse(`[${src}]`);
|
|
43
|
+
}
|
|
44
|
+
catch (e) {
|
|
45
|
+
throw new Error(`Invalid arguments "${src.trim()}": ${e.message}`, { cause: e });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=method.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"method.js","sourceRoot":"","sources":["../../../src/formats/markdown/method.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAsB1B,yEAAyE;AACzE,MAAM,UAAU,YAAY,CAAC,GAAW;IACtC,OAAO,uBAAuB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC3C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,MAAM,CAAC,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IACrB,MAAM,IAAI,GAAG,sBAAsB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC5C,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,sBAAsB,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAC/D,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC;QAClB,MAAM,IAAI,KAAK,CAAC,oCAAoC,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAEpE,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,yBAAyB;IAC1D,MAAM,KAAK,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,4BAA4B;IACxD,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC;AACzE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAW;IAC1C,OAAO,GAAG;SACP,KAAK,CAAC,IAAI,CAAC;SACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;SAC1B,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC;SAC7B,GAAG,CAAC,eAAe,CAAC,CAAC;AAC1B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,GAAW;IACtC,IAAI,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,OAAO,EAAE,CAAC;IACjC,IAAI,CAAC;QACH,OAAO,KAAK,CAAC,KAAK,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC;IACjC,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CACb,sBAAsB,GAAG,CAAC,IAAI,EAAE,MAAO,CAAW,CAAC,OAAO,EAAE,EAC5D,EAAE,KAAK,EAAE,CAAC,EAAE,CACb,CAAC;IACJ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type Action } from "../../actions.js";
|
|
2
|
+
import type { RecordableConfig, VoiceoverConfig } from "../../config.js";
|
|
3
|
+
/** A marker: one call lifted out of narration, with its position in the prose. */
|
|
4
|
+
export interface Marker {
|
|
5
|
+
step: Action;
|
|
6
|
+
/** Character offset of the marker within the stripped narration (markers removed). */
|
|
7
|
+
offset: number;
|
|
8
|
+
}
|
|
9
|
+
/** A prose paragraph: the narration TTS will read, plus its inline markers. */
|
|
10
|
+
export interface NarrationBlock {
|
|
11
|
+
type: "narration";
|
|
12
|
+
narration: string;
|
|
13
|
+
markers: Marker[];
|
|
14
|
+
}
|
|
15
|
+
/** A fenced code block: a pure ordered action list, no narration or timing. */
|
|
16
|
+
export interface ActionsBlock {
|
|
17
|
+
type: "actions";
|
|
18
|
+
actions: Action[];
|
|
19
|
+
}
|
|
20
|
+
export type MarkdownBlock = NarrationBlock | ActionsBlock;
|
|
21
|
+
/** The fully parsed document: recording config, optional voiceover, blocks. */
|
|
22
|
+
export interface ParsedMarkdown {
|
|
23
|
+
config: RecordableConfig;
|
|
24
|
+
voiceover?: VoiceoverConfig;
|
|
25
|
+
blocks: MarkdownBlock[];
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Parse a Markdown document into config + ordered blocks. Pure: no audio, no
|
|
29
|
+
* network. Frontmatter (YAML, via gray-matter) carries recording config and an
|
|
30
|
+
* optional `voiceover` block; everything else is body content.
|
|
31
|
+
*/
|
|
32
|
+
export declare function parseMarkdown(md: string): ParsedMarkdown;
|
|
33
|
+
/**
|
|
34
|
+
* Turn one prose paragraph into narration + markers. Call-shaped backtick spans
|
|
35
|
+
* are lifted out as markers; their character offset into the *stripped*
|
|
36
|
+
* narration (what TTS reads) is preserved for the compiler. Non-call backtick
|
|
37
|
+
* spans stay in the prose verbatim. Whitespace is collapsed without shifting any
|
|
38
|
+
* recorded offset.
|
|
39
|
+
*/
|
|
40
|
+
export declare function narrationBlock(raw: string): NarrationBlock;
|
|
41
|
+
/** Extract a plain action list from parsed blocks (markers in order, narration
|
|
42
|
+
* and timing discarded; no audio). */
|
|
43
|
+
export declare function extractActions(blocks: MarkdownBlock[]): Action[];
|
|
44
|
+
//# sourceMappingURL=parse.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../../../src/formats/markdown/parse.ts"],"names":[],"mappings":"AAGA,OAAO,EAAgB,KAAK,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AA0BzE,kFAAkF;AAClF,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,sFAAsF;IACtF,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,+EAA+E;AAC/E,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,WAAW,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,+EAA+E;AAC/E,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,MAAM,aAAa,GAAG,cAAc,GAAG,YAAY,CAAC;AAE1D,+EAA+E;AAC/E,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,gBAAgB,CAAC;IACzB,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,MAAM,EAAE,aAAa,EAAE,CAAC;CACzB;AAMD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,cAAc,CAqBxD;AA0BD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,CAE1D;AAuDD;uCACuC;AACvC,wBAAgB,cAAc,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,MAAM,EAAE,CAOhE"}
|