tutorial-forge 0.1.1 → 0.2.1
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/dist/config.d.ts +6 -0
- package/dist/config.js +5 -0
- package/dist/config.js.map +1 -1
- package/dist/i18n.d.ts +14 -0
- package/dist/i18n.js +48 -0
- package/dist/i18n.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/pipeline/post.js +4 -2
- package/dist/pipeline/post.js.map +1 -1
- package/dist/pipeline/record.d.ts +4 -0
- package/dist/pipeline/record.js +21 -9
- package/dist/pipeline/record.js.map +1 -1
- package/dist/pipeline/render.js +7 -1
- package/dist/pipeline/render.js.map +1 -1
- package/dist/types.d.ts +22 -6
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
package/dist/config.d.ts
CHANGED
|
@@ -19,6 +19,12 @@ export interface ForgeConfig {
|
|
|
19
19
|
keepWorkDir?: boolean;
|
|
20
20
|
ttsCacheDir?: string;
|
|
21
21
|
ttsConcurrency?: number;
|
|
22
|
+
/** Languages rendered by default (overridable with --lang). Omit for source-language only. */
|
|
23
|
+
languages?: string[];
|
|
24
|
+
/** The language tutorial narration is written in. Default 'en'. */
|
|
25
|
+
defaultLang?: string;
|
|
26
|
+
/** Per-language TTS provider override (e.g. a different voice per language). Falls back to tts. */
|
|
27
|
+
ttsByLang?: Record<string, TTSProvider>;
|
|
22
28
|
}
|
|
23
29
|
export declare function defineConfig(config: ForgeConfig): ForgeConfig;
|
|
24
30
|
/** Validate a loaded config object (e.g. from forge.config.ts). Throws with a readable message. */
|
package/dist/config.js
CHANGED
|
@@ -20,6 +20,11 @@ const configSchema = z.object({
|
|
|
20
20
|
keepWorkDir: z.boolean().optional(),
|
|
21
21
|
ttsCacheDir: z.string().optional(),
|
|
22
22
|
ttsConcurrency: z.number().int().positive().optional(),
|
|
23
|
+
languages: z.array(z.string().min(2)).optional(),
|
|
24
|
+
defaultLang: z.string().min(2).optional(),
|
|
25
|
+
ttsByLang: z
|
|
26
|
+
.record(z.object({ cacheKey: z.string().min(1), synthesize: z.function() }))
|
|
27
|
+
.optional(),
|
|
23
28
|
});
|
|
24
29
|
export function defineConfig(config) {
|
|
25
30
|
return config;
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
package/dist/i18n.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Tutorial } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Return a copy of the tutorial with narration swapped to the given language.
|
|
4
|
+
* Translation tables are keyed by step id — give steps explicit ids so tables
|
|
5
|
+
* stay stable when steps are reordered.
|
|
6
|
+
*
|
|
7
|
+
* - lang === defaultLang → the spec's own narration, unchanged.
|
|
8
|
+
* - Missing entries fall back to the source narration with a warning.
|
|
9
|
+
* - A language with no table at all throws: rendering a wholly untranslated
|
|
10
|
+
* tutorial under a language suffix would silently produce a mislabeled video.
|
|
11
|
+
*/
|
|
12
|
+
export declare function localizeTutorial(tutorial: Tutorial, lang: string, defaultLang?: string): Tutorial;
|
|
13
|
+
/** Languages a tutorial can render in: the default language plus every translation table. */
|
|
14
|
+
export declare function availableLanguages(tutorial: Tutorial, defaultLang?: string): string[];
|
package/dist/i18n.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { stepId } from './spec.js';
|
|
2
|
+
import { logger } from './util/logger.js';
|
|
3
|
+
/**
|
|
4
|
+
* Return a copy of the tutorial with narration swapped to the given language.
|
|
5
|
+
* Translation tables are keyed by step id — give steps explicit ids so tables
|
|
6
|
+
* stay stable when steps are reordered.
|
|
7
|
+
*
|
|
8
|
+
* - lang === defaultLang → the spec's own narration, unchanged.
|
|
9
|
+
* - Missing entries fall back to the source narration with a warning.
|
|
10
|
+
* - A language with no table at all throws: rendering a wholly untranslated
|
|
11
|
+
* tutorial under a language suffix would silently produce a mislabeled video.
|
|
12
|
+
*/
|
|
13
|
+
export function localizeTutorial(tutorial, lang, defaultLang = 'en') {
|
|
14
|
+
if (lang === defaultLang)
|
|
15
|
+
return tutorial;
|
|
16
|
+
const table = tutorial.translations?.[lang];
|
|
17
|
+
if (!table) {
|
|
18
|
+
const available = Object.keys(tutorial.translations ?? {});
|
|
19
|
+
throw new Error(`Tutorial "${tutorial.id}" has no translations for "${lang}"` +
|
|
20
|
+
(available.length ? ` (available: ${available.join(', ')})` : '') +
|
|
21
|
+
` — add a <tutorial-file>.${lang}.json sidecar or a translations entry`);
|
|
22
|
+
}
|
|
23
|
+
const knownIds = new Set(tutorial.steps.map((s, i) => stepId(s, i)));
|
|
24
|
+
const unknown = Object.keys(table).filter((k) => !knownIds.has(k));
|
|
25
|
+
if (unknown.length) {
|
|
26
|
+
logger.warn(`Tutorial "${tutorial.id}" [${lang}]: translation keys match no step: ${unknown.join(', ')}`);
|
|
27
|
+
}
|
|
28
|
+
const missing = [];
|
|
29
|
+
const steps = tutorial.steps.map((step, i) => {
|
|
30
|
+
const id = stepId(step, i);
|
|
31
|
+
const translated = table[id];
|
|
32
|
+
if (translated === undefined) {
|
|
33
|
+
if (step.narration.trim())
|
|
34
|
+
missing.push(id);
|
|
35
|
+
return step;
|
|
36
|
+
}
|
|
37
|
+
return { ...step, narration: translated };
|
|
38
|
+
});
|
|
39
|
+
if (missing.length) {
|
|
40
|
+
logger.warn(`Tutorial "${tutorial.id}" [${lang}]: no translation for step(s) ${missing.join(', ')} — using source narration`);
|
|
41
|
+
}
|
|
42
|
+
return { ...tutorial, steps };
|
|
43
|
+
}
|
|
44
|
+
/** Languages a tutorial can render in: the default language plus every translation table. */
|
|
45
|
+
export function availableLanguages(tutorial, defaultLang = 'en') {
|
|
46
|
+
return [defaultLang, ...Object.keys(tutorial.translations ?? {}).filter((l) => l !== defaultLang)];
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=i18n.js.map
|
package/dist/i18n.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n.js","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACnC,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE1C;;;;;;;;;GASG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAAkB,EAAE,IAAY,EAAE,WAAW,GAAG,IAAI;IACnF,IAAI,IAAI,KAAK,WAAW;QAAE,OAAO,QAAQ,CAAC;IAE1C,MAAM,KAAK,GAAG,QAAQ,CAAC,YAAY,EAAE,CAAC,IAAI,CAAC,CAAC;IAC5C,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC;QAC3D,MAAM,IAAI,KAAK,CACb,aAAa,QAAQ,CAAC,EAAE,8BAA8B,IAAI,GAAG;YAC3D,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,gBAAgB,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YACjE,4BAA4B,IAAI,uCAAuC,CAC1E,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IACrE,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACnE,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACnB,MAAM,CAAC,IAAI,CACT,aAAa,QAAQ,CAAC,EAAE,MAAM,IAAI,sCAAsC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAC7F,CAAC;IACJ,CAAC;IAED,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE;QAC3C,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QAC3B,MAAM,UAAU,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC;QAC7B,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;YAC7B,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE;gBAAE,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC5C,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,EAAE,GAAG,IAAI,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC;IAC5C,CAAC,CAAC,CAAC;IACH,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACnB,MAAM,CAAC,IAAI,CACT,aAAa,QAAQ,CAAC,EAAE,MAAM,IAAI,iCAAiC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,2BAA2B,CACjH,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,GAAG,QAAQ,EAAE,KAAK,EAAE,CAAC;AAChC,CAAC;AAED,6FAA6F;AAC7F,MAAM,UAAU,kBAAkB,CAAC,QAAkB,EAAE,WAAW,GAAG,IAAI;IACvE,OAAO,CAAC,WAAW,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,WAAW,CAAC,CAAC,CAAC;AACrG,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
export type { Tutorial, Step, TutorialAdapter, TTSProvider, RenderOptions, TimingManifest, ManifestStep, CalloutRecord, } from './types.js';
|
|
1
|
+
export type { Tutorial, Step, StepContext, TutorialAdapter, TTSProvider, RenderOptions, TimingManifest, ManifestStep, CalloutRecord, } from './types.js';
|
|
2
2
|
export { StepError } from './types.js';
|
|
3
3
|
export { tutorial, step, validateTutorial, stepId } from './spec.js';
|
|
4
|
+
export { localizeTutorial, availableLanguages } from './i18n.js';
|
|
4
5
|
export { defineConfig, validateConfig, type ForgeConfig } from './config.js';
|
|
5
6
|
export { render, type RenderResult } from './pipeline/render.js';
|
|
6
7
|
export { runTTSPhase, loadTTSResult } from './pipeline/tts.js';
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Public API surface — re-exports only.
|
|
2
2
|
export { StepError } from './types.js';
|
|
3
3
|
export { tutorial, step, validateTutorial, stepId } from './spec.js';
|
|
4
|
+
export { localizeTutorial, availableLanguages } from './i18n.js';
|
|
4
5
|
export { defineConfig, validateConfig } from './config.js';
|
|
5
6
|
export { render } from './pipeline/render.js';
|
|
6
7
|
export { runTTSPhase, loadTTSResult } from './pipeline/tts.js';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,wCAAwC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,wCAAwC;AAaxC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACrE,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,EAAE,YAAY,EAAE,cAAc,EAAoB,MAAM,aAAa,CAAC;AAE7E,OAAO,EAAE,MAAM,EAAqB,MAAM,sBAAsB,CAAC;AACjE,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAC/D,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACnG,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,KAAK,EAAqB,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EAAE,UAAU,EAA0B,MAAM,qBAAqB,CAAC;AACzE,OAAO,EAAE,SAAS,EAAyB,MAAM,iBAAiB,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEnE,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACrE,OAAO,EACL,cAAc,EACd,eAAe,EACf,mBAAmB,EACnB,sBAAsB,EACtB,cAAc,EACd,aAAa,GAEd,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC"}
|
package/dist/pipeline/post.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { join, dirname, basename, extname } from 'node:path';
|
|
2
2
|
import { writeFile } from 'node:fs/promises';
|
|
3
|
-
import { RAW_VIDEO_FILE } from './record.js';
|
|
3
|
+
import { RAW_VIDEO_FILE, FLASH_MS } from './record.js';
|
|
4
4
|
import { buildMergeArgs, detectFlashOffsetMs, probeDurationMs, runFfmpeg } from '../post/ffmpeg.js';
|
|
5
5
|
import { generateSrt } from '../post/subtitles.js';
|
|
6
6
|
import { ensureDir, exists } from '../util/fs.js';
|
|
@@ -17,7 +17,9 @@ export async function runPostPhase(manifest, opts) {
|
|
|
17
17
|
const firstStep = manifest.steps[0];
|
|
18
18
|
if (!firstStep)
|
|
19
19
|
throw new Error('Manifest has no steps');
|
|
20
|
-
|
|
20
|
+
// Never trim before the calibration flash has fully cleared (plus a couple
|
|
21
|
+
// of frames of margin), or magenta frames leak into the output.
|
|
22
|
+
const trimStartMs = Math.max(FLASH_MS + 200, firstStep.startMs - opts.leadInMs);
|
|
21
23
|
let videoOffsetMs = await detectFlashOffsetMs(rawVideo);
|
|
22
24
|
if (videoOffsetMs === null) {
|
|
23
25
|
logger.warn('calibration flash not found in recording — assuming video starts at clock zero; audio sync may drift by the context-creation gap');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"post.js","sourceRoot":"","sources":["../../src/pipeline/post.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7D,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAE7C,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"post.js","sourceRoot":"","sources":["../../src/pipeline/post.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,6 +2,8 @@ import type { TimingManifest, Tutorial, TutorialAdapter } from '../types.js';
|
|
|
2
2
|
import type { TTSPhaseResult } from './tts.js';
|
|
3
3
|
export declare const RAW_VIDEO_FILE = "raw.webm";
|
|
4
4
|
export declare const MANIFEST_FILE = "manifest.json";
|
|
5
|
+
/** Calibration flash duration; the post phase must never trim inside it. */
|
|
6
|
+
export declare const FLASH_MS = 120;
|
|
5
7
|
export interface RecordPhaseOptions {
|
|
6
8
|
workDir: string;
|
|
7
9
|
viewport: {
|
|
@@ -12,6 +14,8 @@ export interface RecordPhaseOptions {
|
|
|
12
14
|
cursor: boolean;
|
|
13
15
|
callouts: boolean;
|
|
14
16
|
leadInMs: number;
|
|
17
|
+
/** Language being rendered; exposed to adapter and step callbacks via ctx. */
|
|
18
|
+
lang?: string;
|
|
15
19
|
}
|
|
16
20
|
/**
|
|
17
21
|
* Phase 2 — drive the browser through the tutorial while Playwright records
|
package/dist/pipeline/record.js
CHANGED
|
@@ -11,8 +11,11 @@ import { ensureDir } from '../util/fs.js';
|
|
|
11
11
|
import { logger } from '../util/logger.js';
|
|
12
12
|
export const RAW_VIDEO_FILE = 'raw.webm';
|
|
13
13
|
export const MANIFEST_FILE = 'manifest.json';
|
|
14
|
+
/** Calibration flash duration; the post phase must never trim inside it. */
|
|
15
|
+
export const FLASH_MS = 120;
|
|
14
16
|
const FINAL_HOLD_MS = 1000;
|
|
15
|
-
|
|
17
|
+
/** Gap after the flash before setup, so the trim point can't land in the flash. */
|
|
18
|
+
const POST_FLASH_HOLD_MS = 400;
|
|
16
19
|
/**
|
|
17
20
|
* Phase 2 — drive the browser through the tutorial while Playwright records
|
|
18
21
|
* video, pacing each step to its narration budget. Writes raw.webm and
|
|
@@ -50,8 +53,16 @@ export async function runRecordPhase(tutorial, adapter, tts, opts) {
|
|
|
50
53
|
resolve();
|
|
51
54
|
}, ms));
|
|
52
55
|
}, FLASH_MS);
|
|
53
|
-
|
|
54
|
-
|
|
56
|
+
// Keep the flash clear of the trim point even when setup is instant:
|
|
57
|
+
// post trims at steps[0].startMs - leadInMs, which without this hold can
|
|
58
|
+
// land inside the flash and leave magenta frames in the output.
|
|
59
|
+
await page.waitForTimeout(POST_FLASH_HOLD_MS);
|
|
60
|
+
const ctx = { lang: opts.lang };
|
|
61
|
+
logger.info(`record: setup (${adapter.baseURL})${opts.lang ? ` [${opts.lang}]` : ''}`);
|
|
62
|
+
await adapter.setup(page, ctx);
|
|
63
|
+
// The final video begins leadInMs before step 1. Hold past that window
|
|
64
|
+
// now so it shows the settled app, not the tail end of setup.
|
|
65
|
+
await page.waitForTimeout(opts.leadInMs + 200);
|
|
55
66
|
const callouts = tutorial.steps.map(() => []);
|
|
56
67
|
let currentStep = 0;
|
|
57
68
|
const instrumented = instrumentPage(page, {
|
|
@@ -74,12 +85,12 @@ export async function runRecordPhase(tutorial, adapter, tts, opts) {
|
|
|
74
85
|
await page.waitForTimeout(opts.leadInMs);
|
|
75
86
|
const actionStartMs = clock.now();
|
|
76
87
|
try {
|
|
77
|
-
await step.run(instrumented);
|
|
78
|
-
await step.waitFor?.(instrumented);
|
|
88
|
+
await step.run(instrumented, ctx);
|
|
89
|
+
await step.waitFor?.(instrumented, ctx);
|
|
79
90
|
}
|
|
80
91
|
catch (cause) {
|
|
81
92
|
await captureFailure(page, opts.workDir, id);
|
|
82
|
-
await saveManifest(tutorial, clock, manifestSteps, opts.workDir);
|
|
93
|
+
await saveManifest(tutorial, clock, manifestSteps, opts.workDir, opts.lang);
|
|
83
94
|
await safeClose(context.close());
|
|
84
95
|
throw new StepError(tutorial.id, id, cause);
|
|
85
96
|
}
|
|
@@ -107,10 +118,10 @@ export async function runRecordPhase(tutorial, adapter, tts, opts) {
|
|
|
107
118
|
});
|
|
108
119
|
}
|
|
109
120
|
await page.waitForTimeout(FINAL_HOLD_MS);
|
|
110
|
-
const manifest = await saveManifest(tutorial, clock, manifestSteps, opts.workDir);
|
|
121
|
+
const manifest = await saveManifest(tutorial, clock, manifestSteps, opts.workDir, opts.lang);
|
|
111
122
|
if (adapter.teardown) {
|
|
112
123
|
try {
|
|
113
|
-
await adapter.teardown(page);
|
|
124
|
+
await adapter.teardown(page, ctx);
|
|
114
125
|
}
|
|
115
126
|
catch (err) {
|
|
116
127
|
logger.warn(`teardown failed (ignored): ${err instanceof Error ? err.message : err}`);
|
|
@@ -136,9 +147,10 @@ async function launchChromium(headless) {
|
|
|
136
147
|
return chromium.launch({ headless });
|
|
137
148
|
}
|
|
138
149
|
}
|
|
139
|
-
async function saveManifest(tutorial, clock, steps, workDir) {
|
|
150
|
+
async function saveManifest(tutorial, clock, steps, workDir, lang) {
|
|
140
151
|
const manifest = {
|
|
141
152
|
tutorialId: tutorial.id,
|
|
153
|
+
...(lang ? { lang } : {}),
|
|
142
154
|
fps: 25,
|
|
143
155
|
recordingStartEpochMs: clock.zeroEpoch,
|
|
144
156
|
steps,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"record.js","sourceRoot":"","sources":["../../src/pipeline/record.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,EAAE,QAAQ,EAA2B,MAAM,YAAY,CAAC;AAE/D,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEpC,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvE,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAE3C,MAAM,CAAC,MAAM,cAAc,GAAG,UAAU,CAAC;AACzC,MAAM,CAAC,MAAM,aAAa,GAAG,eAAe,CAAC;AAC7C,MAAM,aAAa,GAAG,IAAI,CAAC;AAC3B,MAAM,
|
|
1
|
+
{"version":3,"file":"record.js","sourceRoot":"","sources":["../../src/pipeline/record.ts"],"names":[],"mappings":"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"}
|
package/dist/pipeline/render.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { resolve, join } from 'node:path';
|
|
2
2
|
import { validateTutorial } from '../spec.js';
|
|
3
|
+
import { localizeTutorial } from '../i18n.js';
|
|
3
4
|
import { runTTSPhase, loadTTSResult } from './tts.js';
|
|
4
5
|
import { runRecordPhase, loadManifest } from './record.js';
|
|
5
6
|
import { runPostPhase } from './post.js';
|
|
@@ -13,7 +14,11 @@ import { logger } from '../util/logger.js';
|
|
|
13
14
|
*/
|
|
14
15
|
export async function render(tutorial, adapter, options) {
|
|
15
16
|
validateTutorial(tutorial);
|
|
16
|
-
const
|
|
17
|
+
const lang = options.lang;
|
|
18
|
+
if (lang) {
|
|
19
|
+
tutorial = localizeTutorial(tutorial, lang, options.defaultLang ?? 'en');
|
|
20
|
+
}
|
|
21
|
+
const workDir = resolve(options.workDir ?? join('.forge', lang ? `${tutorial.id}.${lang}` : tutorial.id));
|
|
17
22
|
const output = resolve(options.output);
|
|
18
23
|
const viewport = options.viewport ?? { width: 1920, height: 1080 };
|
|
19
24
|
const leadInMs = options.leadInMs ?? 300;
|
|
@@ -39,6 +44,7 @@ export async function render(tutorial, adapter, options) {
|
|
|
39
44
|
cursor: options.cursor ?? true,
|
|
40
45
|
callouts: options.callouts ?? true,
|
|
41
46
|
leadInMs,
|
|
47
|
+
lang,
|
|
42
48
|
})
|
|
43
49
|
: await loadManifest(workDir);
|
|
44
50
|
if (phase === 'record') {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render.js","sourceRoot":"","sources":["../../src/pipeline/render.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAE1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAwB,MAAM,WAAW,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAO3C;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,MAAM,CAC1B,QAAkB,EAClB,OAAwB,EACxB,OAAsB;IAEtB,gBAAgB,CAAC,QAAQ,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"render.js","sourceRoot":"","sources":["../../src/pipeline/render.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,22 +1,27 @@
|
|
|
1
1
|
import type { Page } from 'playwright';
|
|
2
|
+
/** Passed to adapter and step callbacks; lets app code react to the render language. */
|
|
3
|
+
export interface StepContext {
|
|
4
|
+
/** The language being rendered (from RenderOptions.lang / --lang), if any. */
|
|
5
|
+
lang?: string;
|
|
6
|
+
}
|
|
2
7
|
/** Gets the target app into a known, recordable state. The only app-specific code. */
|
|
3
8
|
export interface TutorialAdapter {
|
|
4
9
|
/** Base URL of the running app, e.g. http://localhost:3000 */
|
|
5
10
|
baseURL: string;
|
|
6
11
|
/** Auth, seeding, navigation to a starting screen. Runs after page creation, before step 1. Excluded from the final video by default. */
|
|
7
|
-
setup(page: Page): Promise<void>;
|
|
12
|
+
setup(page: Page, ctx: StepContext): Promise<void>;
|
|
8
13
|
/** Optional cleanup after recording (delete seeded data, logout). Never recorded. */
|
|
9
|
-
teardown?(page: Page): Promise<void>;
|
|
14
|
+
teardown?(page: Page, ctx: StepContext): Promise<void>;
|
|
10
15
|
}
|
|
11
16
|
export interface Step {
|
|
12
|
-
/** Stable id, auto-derived from index if omitted. Used in manifest, cache keys, logs. */
|
|
17
|
+
/** Stable id, auto-derived from index if omitted. Used in manifest, cache keys, logs, translation tables. */
|
|
13
18
|
id?: string;
|
|
14
|
-
/** The narration line spoken over this step. Plain text; may be ''. */
|
|
19
|
+
/** The narration line spoken over this step, in the source language. Plain text; may be ''. */
|
|
15
20
|
narration: string;
|
|
16
21
|
/** The action. Receives the raw Playwright Page. May be a no-op for pure-narration steps. */
|
|
17
|
-
run: (page: Page) => Promise<void>;
|
|
22
|
+
run: (page: Page, ctx: StepContext) => Promise<void>;
|
|
18
23
|
/** Optional readiness hook awaited after run(); use when auto-waiting isn't enough. */
|
|
19
|
-
waitFor?: (page: Page) => Promise<void>;
|
|
24
|
+
waitFor?: (page: Page, ctx: StepContext) => Promise<void>;
|
|
20
25
|
/** Extra hold time (ms) after both narration and action complete. Default 400. */
|
|
21
26
|
settleMs?: number;
|
|
22
27
|
}
|
|
@@ -26,6 +31,11 @@ export interface Tutorial {
|
|
|
26
31
|
title: string;
|
|
27
32
|
description?: string;
|
|
28
33
|
steps: Step[];
|
|
34
|
+
/**
|
|
35
|
+
* Per-language narration overrides: lang → (step id → translated line).
|
|
36
|
+
* Usually loaded from sidecar files (<tutorial-file>.<lang>.json) by the CLI.
|
|
37
|
+
*/
|
|
38
|
+
translations?: Record<string, Record<string, string>>;
|
|
29
39
|
}
|
|
30
40
|
export interface TTSProvider {
|
|
31
41
|
/** Unique key for cache partitioning, e.g. "elevenlabs:daniel:eleven_turbo_v2" */
|
|
@@ -62,6 +72,10 @@ export interface RenderOptions {
|
|
|
62
72
|
ttsConcurrency?: number;
|
|
63
73
|
/** Which phases to run. Default 'all'. */
|
|
64
74
|
phase?: 'tts' | 'record' | 'post' | 'all';
|
|
75
|
+
/** Render this language: narration comes from tutorial.translations[lang] and ctx.lang is set. */
|
|
76
|
+
lang?: string;
|
|
77
|
+
/** The language the spec's narration strings are written in. Default 'en'. */
|
|
78
|
+
defaultLang?: string;
|
|
65
79
|
}
|
|
66
80
|
export interface CalloutRecord {
|
|
67
81
|
atMs: number;
|
|
@@ -89,6 +103,8 @@ export interface ManifestStep {
|
|
|
89
103
|
/** Written to workDir as manifest.json; the contract between record and post phases. */
|
|
90
104
|
export interface TimingManifest {
|
|
91
105
|
tutorialId: string;
|
|
106
|
+
/** Language this render used, if localized. */
|
|
107
|
+
lang?: string;
|
|
92
108
|
fps: number;
|
|
93
109
|
recordingStartEpochMs: number;
|
|
94
110
|
/** Offset (ms) into the raw webm where the recording clock's zero falls, derived from the calibration flash. 0 if undetected. */
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAyHA,qEAAqE;AACrE,MAAM,OAAO,SAAU,SAAQ,KAAK;IAEhB;IACA;IACS;IAH3B,YACkB,UAAkB,EAClB,MAAc,EACL,KAAc;QAEvC,KAAK,CACH,SAAS,MAAM,kBAAkB,UAAU,aAAa,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CACjH,CAAC;QANc,eAAU,GAAV,UAAU,CAAQ;QAClB,WAAM,GAAN,MAAM,CAAQ;QACL,UAAK,GAAL,KAAK,CAAS;QAKvC,IAAI,CAAC,IAAI,GAAG,WAAW,CAAC;IAC1B,CAAC;CACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tutorial-forge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Turn scripted Playwright walkthroughs into narrated tutorial videos (MP4). Tutorials are source code: re-render instead of re-recording when your UI changes.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|