retiam 2.0.2 → 2.0.3
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/agent/AGENTS.md +4 -1
- package/agent/skill/SKILL.md +7 -4
- package/agent/skill/examples/NarratedMotion.ts +10 -2
- package/agent/skill/references/api.md +4 -2
- package/agent/skill/references/explainers.md +4 -2
- package/agent/skill/references/quality.md +3 -0
- package/agent/skill/references/tts.md +154 -60
- package/bin/lesson.js +39 -12
- package/dist/{UTIF-BrQ3Ng5l.min.js → UTIF-D6vx2UEX.min.js} +1 -1
- package/dist/{UTIF-CN0-pEnd.js → UTIF-mkffT70R.js} +1 -1
- package/dist/{index-yQIdejOj.js → index-CdBPrYbr.js} +3818 -3785
- package/dist/{index-3sAseSJQ.min.js → index-jgFyrc2D.min.js} +175 -175
- package/dist/libs/explainer/index.js +74 -73
- package/dist/libs/explainer/plan.js +162 -140
- package/dist/libs/explainer/types.d.ts +5 -0
- package/dist/libs/tts/animations/Speak.d.ts +13 -7
- package/dist/libs/tts/components/TTSNarrationComponent.d.ts +9 -7
- package/dist/libs/tts/core/TTSCache.d.ts +3 -3
- package/dist/libs/tts/core/TTSClient.d.ts +3 -3
- package/dist/libs/tts/core/types.d.ts +48 -6
- package/dist/libs/tts/index.d.ts +2 -1
- package/dist/libs/tts/index.js +165 -672
- package/dist/libs/tts/node/index.d.ts +10 -0
- package/dist/libs/tts/node/index.js +791 -0
- package/dist/libs/tts/providers/ElevenLabsTTSProvider.d.ts +30 -0
- package/dist/libs/tts/providers/ITTSProvider.d.ts +2 -2
- package/dist/libs/vite/index.js +1 -1
- package/dist/{main-lQwYAed8.min.js → main-Cc4QQbSK.min.js} +1 -1
- package/dist/{main-BTk_9jtl.js → main-DMu_5Qy7.js} +1 -1
- package/dist/{msEdgeV2-BLEnvPJa.js → msEdgeV2-D3w4Xsn-.js} +30 -29
- package/dist/retiam.es.js +1 -1
- package/dist/retiam.es.min.js +1 -1
- package/dist/src/engine/entities/composites/text/mathjaxInterop.d.ts +6 -0
- package/package.json +4 -2
- package/readme.md +20 -1
|
@@ -1,112 +1,113 @@
|
|
|
1
|
-
import { assertLesson as S, voiceKey as
|
|
1
|
+
import { assertLesson as S, voiceKey as M, speechKey as y, compileLesson as E, resolveStyle as A } from "./plan.js";
|
|
2
2
|
import { STYLE_PROFILES as ae, lessonCaptions as oe, validateLesson as se } from "./plan.js";
|
|
3
|
-
import { CustomAnimation as
|
|
4
|
-
import { TTS as
|
|
3
|
+
import { CustomAnimation as x, TrackAnimation as q, Waiting as C, hex as v, Color as $, Excalidraw as z, Create as N, Tex as D, CENTER as L, Rate as k } from "retiam";
|
|
4
|
+
import { TTS as T } from "retiam/tts";
|
|
5
5
|
/*! Retiam: noncommercial use only unless separately licensed. See ../../LICENSE and ../../COMMERCIAL_LICENSE.md. Third-party notices: ../../THIRD_PARTY_NOTICES.md and ../../THIRD_PARTY_LICENSES.txt. */
|
|
6
6
|
function Y(r, t, i = {}) {
|
|
7
7
|
S(t);
|
|
8
|
-
const
|
|
8
|
+
const u = (async () => {
|
|
9
9
|
const s = /* @__PURE__ */ new Map();
|
|
10
10
|
let a = i.prepared;
|
|
11
11
|
if (t.narration.enabled) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
const f = t.narration.provider ?? "msedge";
|
|
13
|
+
if (!a && !i.synthesize && T.provider.name !== (f === "msedge" ? "msedge-tts" : f))
|
|
14
|
+
throw new Error("Initialize and select the lesson narration provider, or provide prepared speech or a synthesize adapter.");
|
|
15
|
+
const c = r.audio;
|
|
16
|
+
if (a && a.voiceKey !== M(t))
|
|
16
17
|
throw new Error("Prepared narration uses different voice settings. Prepare narration again.");
|
|
17
|
-
a = a ?? { version: 1, voiceKey:
|
|
18
|
-
const
|
|
18
|
+
a = a ?? { version: 1, voiceKey: M(t), assets: {} };
|
|
19
|
+
const p = t.beats.flatMap((e) => e.narration.map((o) => ({ beat: e.id, segment: o })));
|
|
19
20
|
let w = 0;
|
|
20
|
-
await Promise.all(Array.from({ length: Math.min(3,
|
|
21
|
-
for (; w <
|
|
22
|
-
const
|
|
23
|
-
let
|
|
21
|
+
await Promise.all(Array.from({ length: Math.min(3, p.length) }, async () => {
|
|
22
|
+
for (; w < p.length; ) {
|
|
23
|
+
const e = p[w++], o = y(e.beat, e.segment.id);
|
|
24
|
+
let g;
|
|
24
25
|
if (i.prepared) {
|
|
25
|
-
const
|
|
26
|
-
if (!
|
|
27
|
-
throw new Error(`Missing/stale prepared asset: ${
|
|
28
|
-
const
|
|
29
|
-
if (!
|
|
30
|
-
throw new Error(`Narration asset ${
|
|
31
|
-
if (
|
|
32
|
-
throw new Error(`Narration duration mismatch: ${
|
|
26
|
+
const d = a.assets[o];
|
|
27
|
+
if (!d || d.text !== e.segment.text || (d.speechText ?? d.text) !== (e.segment.speechText ?? e.segment.text) || !d.url)
|
|
28
|
+
throw new Error(`Missing/stale prepared asset: ${o}.`);
|
|
29
|
+
const l = await fetch(d.url);
|
|
30
|
+
if (!l.ok)
|
|
31
|
+
throw new Error(`Narration asset ${d.url}: HTTP ${l.status}.`);
|
|
32
|
+
if (g = await c.context.decodeAudioData(await l.arrayBuffer()), Math.abs(g.duration * 1e3 - d.durationMs) > 120)
|
|
33
|
+
throw new Error(`Narration duration mismatch: ${o}.`);
|
|
33
34
|
} else {
|
|
34
|
-
const
|
|
35
|
-
a.assets[
|
|
35
|
+
const d = e.segment.speechText ?? e.segment.text, l = await (i.synthesize?.(d) ?? T.synthesize(d, { voice: t.narration.voice ?? (f === "msedge" ? "en-US-AndrewNeural" : void 0), speed: t.narration.speed, pitch: t.narration.pitch, volume: 1, displayText: e.segment.text, elevenlabs: t.narration.elevenlabs }));
|
|
36
|
+
a.assets[o] = { text: e.segment.text, speechText: e.segment.speechText, durationMs: l.durationMs, words: (l.wordBoundaries ?? []).map((n) => ({ text: n.text, offsetMs: n.offsetMs, durationMs: n.durationMs })) }, g = l.rawBuffer ? await c.context.decodeAudioData(l.rawBuffer.slice(0)) : l.toAudioBuffer(c.context);
|
|
36
37
|
}
|
|
37
|
-
s.set(
|
|
38
|
+
s.set(o, g);
|
|
38
39
|
}
|
|
39
40
|
}));
|
|
40
41
|
}
|
|
41
|
-
const
|
|
42
|
-
return { plan: t, timeline:
|
|
42
|
+
const h = E(t, a, i.fps ?? 60), m = A(t.style);
|
|
43
|
+
return { plan: t, timeline: h, style: m, async run(f) {
|
|
43
44
|
const c = new Set(t.beats.map((e) => e.id));
|
|
44
|
-
for (const e of Object.keys(
|
|
45
|
+
for (const e of Object.keys(f))
|
|
45
46
|
if (!c.has(e))
|
|
46
47
|
throw new Error(`Unknown beat handler '${e}'.`);
|
|
47
|
-
const
|
|
48
|
-
for (const e of
|
|
49
|
-
const
|
|
50
|
-
if (!
|
|
48
|
+
const p = [];
|
|
49
|
+
for (const e of h.beats) {
|
|
50
|
+
const o = f[e.id];
|
|
51
|
+
if (!o)
|
|
51
52
|
throw new Error(`Missing beat handler '${e.id}'.`);
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
if (
|
|
55
|
-
throw new Error(`Unknown cue '${e.id}/${
|
|
56
|
-
return
|
|
57
|
-
},
|
|
58
|
-
}, { delay:
|
|
59
|
-
for (const
|
|
60
|
-
if (!Number.isFinite(
|
|
53
|
+
const g = (n) => {
|
|
54
|
+
const b = e.cues[n];
|
|
55
|
+
if (b === void 0)
|
|
56
|
+
throw new Error(`Unknown cue '${e.id}/${n}'.`);
|
|
57
|
+
return b;
|
|
58
|
+
}, d = o({ beat: e, durationMs: e.durationMs, style: m, cue: g, at: (n, b) => new x(() => {
|
|
59
|
+
}, { delay: g(n), duration: 1, onStart: b }) });
|
|
60
|
+
for (const n of d)
|
|
61
|
+
if (!Number.isFinite(n.delay) || !Number.isFinite(n.duration) || n.delay < 0 || n.duration < 0 || n.delay + n.duration > e.durationMs + 0.1)
|
|
61
62
|
throw new Error(`Animation exceeds beat '${e.id}' (${e.durationMs} ms).`);
|
|
62
|
-
const
|
|
63
|
-
|
|
63
|
+
const l = new q([...d, new C({ duration: e.durationMs })]);
|
|
64
|
+
l.delay = e.startMs, p.push(l);
|
|
64
65
|
}
|
|
65
|
-
const
|
|
66
|
+
const w = r.composer?.time ?? 0;
|
|
66
67
|
if (t.narration.enabled)
|
|
67
|
-
for (const e of
|
|
68
|
-
for (const
|
|
69
|
-
const
|
|
70
|
-
r.audio.timeline.addClip(
|
|
68
|
+
for (const e of h.beats)
|
|
69
|
+
for (const o of e.segments) {
|
|
70
|
+
const g = s.get(y(e.id, o.id));
|
|
71
|
+
r.audio.timeline.addClip(g, w + e.startMs + o.startMs, r.audio.voiceoverGain, { volume: t.narration.volume ?? 1, bus: "voiceover" }), r.audio.timeline.addVolumeAutomation("bgm", w + e.startMs + o.startMs, r.audio.bgmVolume * 0.25, 200), r.audio.timeline.addVolumeAutomation("bgm", w + e.startMs + o.startMs + o.durationMs, r.audio.bgmVolume, 200);
|
|
71
72
|
}
|
|
72
|
-
r.clearColor(
|
|
73
|
+
r.clearColor(v(m.background)), await r.play(new q(p));
|
|
73
74
|
} };
|
|
74
75
|
})();
|
|
75
|
-
return r.composer?.pendingTasks.push(
|
|
76
|
+
return r.composer?.pendingTasks.push(u), u;
|
|
76
77
|
}
|
|
77
|
-
function J(r, t, i = "primary",
|
|
78
|
+
function J(r, t, i = "primary", u = !1) {
|
|
78
79
|
const s = t[i];
|
|
79
|
-
return r.setStroke(t.strokeWidth,
|
|
80
|
+
return r.setStroke(t.strokeWidth, v(s)), r.setFill(u ? v(s).alpha(0.18) : $.NONE), t.rough && r.toExcalidraw({ preset: "clean", stroke: s, strokeWidth: t.strokeWidth, roughness: t.roughness, fill: u ? s : "transparent", fillStyle: t.fillStyle, fillGap: 0.18, fillAlpha: 0.1, seed: 17, boil: !1 }), r;
|
|
80
81
|
}
|
|
81
|
-
function Q(r, t, i = 0,
|
|
82
|
-
return t.rough ?
|
|
82
|
+
function Q(r, t, i = 0, u = t.motion.drawMs) {
|
|
83
|
+
return t.rough ? z.createAnim(r, { delay: i, duration: u }) : new N(r, { delay: i, duration: u });
|
|
83
84
|
}
|
|
84
|
-
function X(r, t, i,
|
|
85
|
-
const
|
|
86
|
-
r.add(
|
|
87
|
-
let
|
|
88
|
-
return new
|
|
89
|
-
|
|
85
|
+
function X(r, t, i, u, s, a = {}) {
|
|
86
|
+
const h = new D(t, { fontSize: 0.42, anchor: L, fill: v(s.primary), opacity: 0 });
|
|
87
|
+
r.add(h);
|
|
88
|
+
let m, f;
|
|
89
|
+
return new x((c) => {
|
|
90
|
+
h.transform.position.set([m[0] + (f[0] - m[0]) * c, m[1] + (f[1] - m[1]) * c + (a.arc ?? 0.45) * Math.sin(Math.PI * c), m[2] + (f[2] - m[2]) * c]), h.setOpacity(Math.min(1, c * 8, (1 - c) * 8));
|
|
90
91
|
}, { delay: a.delay ?? 0, duration: a.duration ?? s.motion.moveMs, rate_func: k.smooth(), onStart: () => {
|
|
91
|
-
|
|
92
|
-
}, onComplete: () => r.remove(
|
|
92
|
+
m = i(), f = u();
|
|
93
|
+
}, onComplete: () => r.remove(h) });
|
|
93
94
|
}
|
|
94
|
-
function Z(r, t = 0, i = 900,
|
|
95
|
+
function Z(r, t = 0, i = 900, u = 0.1) {
|
|
95
96
|
let s;
|
|
96
|
-
return new
|
|
97
|
-
const
|
|
98
|
-
r.transform.setScale([s[0] *
|
|
97
|
+
return new x((a) => {
|
|
98
|
+
const h = 1 + u * Math.sin(Math.PI * a);
|
|
99
|
+
r.transform.setScale([s[0] * h, s[1] * h, s[2]]);
|
|
99
100
|
}, { delay: t, duration: i, rate_func: k.smooth(), onStart: () => {
|
|
100
101
|
const a = r.transform.getScale();
|
|
101
102
|
s = [a.x, a.y, a.z];
|
|
102
103
|
} });
|
|
103
104
|
}
|
|
104
|
-
const
|
|
105
|
-
version:
|
|
106
|
-
title:
|
|
107
|
-
topic:
|
|
108
|
-
audience:
|
|
109
|
-
objective:
|
|
105
|
+
const I = 1, P = "Why does area grow so fast?", B = "How a square's area changes when its side length doubles", F = "Curious beginners", H = "See why doubling both side lengths produces four unit squares.", K = "mathematical", O = { enabled: !1, provider: "msedge", voice: "en-US-AndrewNeural", speed: 1, pitch: "+0Hz", volume: 1 }, V = [], W = [{ id: "area-model", text: "A square of side two can be partitioned into four squares of side one.", sourceIds: [], illustrative: !0 }], j = [{ id: "predict", goal: "Invite a prediction before exposing the area.", visibleChange: "Draw one unit square and mark its side length.", focus: "The original square", minDurationMs: 4500, narration: [{ id: "question", text: "Here is one unit square. If we double its side length, what happens to its area?", pauseAfterMs: 900 }], cues: [{ id: "show", segment: "question" }] }, { id: "grow", goal: "Separate a change in side length from a change in area.", visibleChange: "Expand the outline while retaining the original square.", focus: "Both sides grow", minDurationMs: 5e3, narration: [{ id: "expand", text: "Let's double both sides. The original square still fits in the corner. How much new space have we made?", pauseAfterMs: 700 }], cues: [{ id: "expand", segment: "expand" }] }, { id: "count", goal: "Discover the four equal areas through decomposition.", visibleChange: "Reveal the three additional unit squares in sequence.", focus: "Four equal pieces", minDurationMs: 5e3, narration: [{ id: "reveal", text: "One here, another above, and one more in the corner. Together with our original square, that makes four." }], cues: [{ id: "reveal", segment: "reveal" }] }, { id: "connect", goal: "Connect the construction to the general area relationship.", visibleChange: "Keep all four pieces visible and reveal the equation below them.", focus: "Side length and area", minDurationMs: 4500, narration: [{ id: "insight", text: "Two times the side length gives four times the area. That's the square in side squared: two growing directions, multiplied together." }], cues: [{ id: "equation", segment: "insight" }] }], G = {
|
|
106
|
+
version: I,
|
|
107
|
+
title: P,
|
|
108
|
+
topic: B,
|
|
109
|
+
audience: F,
|
|
110
|
+
objective: H,
|
|
110
111
|
style: K,
|
|
111
112
|
narration: O,
|
|
112
113
|
sources: V,
|
|
@@ -130,5 +131,5 @@ export {
|
|
|
130
131
|
J as styleGeometry,
|
|
131
132
|
X as transferValue,
|
|
132
133
|
se as validateLesson,
|
|
133
|
-
|
|
134
|
+
M as voiceKey
|
|
134
135
|
};
|
|
@@ -1,197 +1,219 @@
|
|
|
1
1
|
/*! Retiam: noncommercial use only unless separately licensed. See ../../LICENSE and ../../COMMERCIAL_LICENSE.md. Third-party notices: ../../THIRD_PARTY_NOTICES.md and ../../THIRD_PARTY_LICENSES.txt. */
|
|
2
|
-
const
|
|
3
|
-
mathematical:
|
|
4
|
-
excalidraw:
|
|
5
|
-
minimal:
|
|
6
|
-
cinematic:
|
|
7
|
-
},
|
|
8
|
-
function
|
|
9
|
-
const
|
|
10
|
-
if (!Object.hasOwn(
|
|
11
|
-
throw new Error(`Unknown style '${
|
|
12
|
-
const e =
|
|
13
|
-
for (const
|
|
14
|
-
if (
|
|
15
|
-
throw new Error(`Unknown style token '${
|
|
16
|
-
if (
|
|
2
|
+
const w = { name: "Dijkstra-style mathematical explainer", background: "#0A1020", ink: "#ECF0F5", muted: "#74869C", primary: "#6CD5FF", secondary: "#F7CF73", font: "roboto", strokeWidth: 0.035, rough: !1, roughness: 0, fillStyle: "solid", motion: { drawMs: 1200, moveMs: 1400, holdMs: 700 }, accents: ["#65F0C4", "#FF879F", "#BA98FF", "#55DDE0"] }, x = { name: "Excalidraw whiteboard", background: "#FFFDF7", ink: "#263238", muted: "#7B858C", primary: "#1971C2", secondary: "#D46B08", font: "excalifont", strokeWidth: 0.04, rough: !0, roughness: 0.7, fillStyle: "hachure", motion: { drawMs: 1600, moveMs: 1500, holdMs: 900 }, accents: ["#1971C2", "#D46B08"] }, k = { name: "Minimal vector", background: "#F8FAFC", ink: "#17212B", muted: "#68788A", primary: "#2563EB", secondary: "#C2410C", font: "roboto", strokeWidth: 0.025, rough: !1, roughness: 0, fillStyle: "solid", motion: { drawMs: 900, moveMs: 1100, holdMs: 650 }, accents: ["#2563EB", "#C2410C"] }, E = { name: "Cinematic diagrams", background: "#080B18", ink: "#F4F1FF", muted: "#8290AA", primary: "#B79AFF", secondary: "#58E0CE", font: "roboto", strokeWidth: 0.04, rough: !1, roughness: 0, fillStyle: "solid", motion: { drawMs: 1500, moveMs: 2e3, holdMs: 1e3 }, accents: ["#B79AFF", "#58E0CE"] }, A = {
|
|
3
|
+
mathematical: w,
|
|
4
|
+
excalidraw: x,
|
|
5
|
+
minimal: k,
|
|
6
|
+
cinematic: E
|
|
7
|
+
}, b = A;
|
|
8
|
+
function F(i) {
|
|
9
|
+
const u = typeof i == "string" ? i : i?.extends;
|
|
10
|
+
if (!Object.hasOwn(b, u ?? ""))
|
|
11
|
+
throw new Error(`Unknown style '${u}'. Choose ${Object.keys(b).join(", ")} or extend one in the lesson JSON.`);
|
|
12
|
+
const e = b[u], s = typeof i == "string" ? {} : i;
|
|
13
|
+
for (const c of Object.keys(s))
|
|
14
|
+
if (c !== "extends" && !Object.hasOwn(e, c))
|
|
15
|
+
throw new Error(`Unknown style token '${c}'.`);
|
|
16
|
+
if (s.motion !== void 0 && (!s.motion || typeof s.motion != "object" || Array.isArray(s.motion)))
|
|
17
17
|
throw new Error("Style motion must be an object.");
|
|
18
|
-
for (const
|
|
19
|
-
if (!Object.hasOwn(e.motion,
|
|
20
|
-
throw new Error(`Unknown style motion token '${
|
|
21
|
-
const
|
|
22
|
-
for (const
|
|
23
|
-
if (!/^#[\da-f]{6}$/i.test(
|
|
24
|
-
throw new Error(`Style ${
|
|
25
|
-
if (!
|
|
18
|
+
for (const c of Object.keys(s.motion ?? {}))
|
|
19
|
+
if (!Object.hasOwn(e.motion, c))
|
|
20
|
+
throw new Error(`Unknown style motion token '${c}'.`);
|
|
21
|
+
const r = { ...e, ...s, motion: { ...e.motion, ...s.motion } };
|
|
22
|
+
for (const c of ["background", "ink", "muted", "primary", "secondary"])
|
|
23
|
+
if (!/^#[\da-f]{6}$/i.test(r[c]))
|
|
24
|
+
throw new Error(`Style ${c} must be a six-digit hex color.`);
|
|
25
|
+
if (!r.font?.trim() || !r.name?.trim() || typeof r.rough != "boolean" || !["solid", "hachure"].includes(r.fillStyle))
|
|
26
26
|
throw new Error("Invalid style font, name, rough flag, or fillStyle.");
|
|
27
|
-
if (!(
|
|
27
|
+
if (!(r.strokeWidth > 0 && r.strokeWidth <= 0.5) || !(r.roughness >= 0 && r.roughness <= 3))
|
|
28
28
|
throw new Error("Invalid style strokeWidth or roughness.");
|
|
29
|
-
for (const [
|
|
30
|
-
if (!Number.isFinite(
|
|
31
|
-
throw new Error(`Invalid style motion.${
|
|
32
|
-
if (!Array.isArray(
|
|
29
|
+
for (const [c, l] of Object.entries(r.motion))
|
|
30
|
+
if (!Number.isFinite(l) || l < 0)
|
|
31
|
+
throw new Error(`Invalid style motion.${c}.`);
|
|
32
|
+
if (!Array.isArray(r.accents) || !r.accents.length || r.accents.some((c) => !/^#[\da-f]{6}$/i.test(c)))
|
|
33
33
|
throw new Error("Style accents must contain hex colors.");
|
|
34
|
-
return { ...
|
|
34
|
+
return { ...r, accents: [...r.accents] };
|
|
35
35
|
}
|
|
36
|
-
const
|
|
37
|
-
function
|
|
38
|
-
const
|
|
39
|
-
if (!
|
|
36
|
+
const y = /^[a-z][a-z0-9-]*$/, h = (i) => typeof i == "string" && i.trim().length > 0, g = (i) => i.toLowerCase().replace(/[^\p{L}\p{N}]/gu, ""), S = (i, u) => `${i}/${u}`, I = (i) => JSON.stringify({ provider: i.narration.provider ?? "msedge", voice: i.narration.voice ?? ((i.narration.provider ?? "msedge") === "msedge" ? "en-US-AndrewNeural" : null), speed: i.narration.speed ?? 1, pitch: i.narration.pitch ?? ((i.narration.provider ?? "msedge") === "msedge" ? "+0Hz" : null), elevenlabs: i.narration.elevenlabs });
|
|
37
|
+
function D(i) {
|
|
38
|
+
const u = [], e = (t, o) => u.push({ path: t, message: o });
|
|
39
|
+
if (!i || typeof i != "object" || Array.isArray(i))
|
|
40
40
|
return [{ path: "$", message: "Expected a lesson object." }];
|
|
41
|
-
const
|
|
42
|
-
|
|
41
|
+
const s = i;
|
|
42
|
+
s.version !== 1 && e("version", "Expected version 1.");
|
|
43
43
|
for (const t of ["title", "topic", "audience", "objective"])
|
|
44
|
-
|
|
44
|
+
h(s[t]) || e(t, "Provide a nonempty string.");
|
|
45
45
|
try {
|
|
46
|
-
|
|
46
|
+
F(s.style);
|
|
47
47
|
} catch (t) {
|
|
48
48
|
e("style", t.message);
|
|
49
49
|
}
|
|
50
|
-
(!
|
|
50
|
+
(!s.narration || typeof s.narration.enabled != "boolean") && e("narration.enabled", "Choose true or false."), s.narration?.speed !== void 0 && (!Number.isFinite(s.narration.speed) || s.narration.speed <= 0) && e("narration.speed", "Must be positive."), s.narration?.volume !== void 0 && (!Number.isFinite(s.narration.volume) || s.narration.volume < 0 || s.narration.volume > 1) && e("narration.volume", "Must be between 0 and 1."), s.narration?.voice !== void 0 && !h(s.narration.voice) && e("narration.voice", "Must be a voice ID.");
|
|
51
51
|
for (const t of ["provider", "pitch"])
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
s.narration?.[t] !== void 0 && !h(s.narration[t]) && e(`narration.${t}`, "Must be a nonempty string.");
|
|
53
|
+
s.narration?.provider === "elevenlabs" && !h(s.narration.voice) && e("narration.voice", "Choose an ElevenLabs voice ID so prepared speech identifies its speaker.");
|
|
54
|
+
const r = s.narration?.elevenlabs;
|
|
55
|
+
if (r !== void 0) {
|
|
56
|
+
const t = "narration.elevenlabs";
|
|
57
|
+
if (!r || typeof r != "object" || Array.isArray(r)) e(t, "Expected ElevenLabs synthesis settings.");
|
|
58
|
+
else {
|
|
59
|
+
const o = ["modelId", "voiceSettings", "languageCode", "seed", "previousText", "nextText", "applyTextNormalization", "pronunciationDictionaries"];
|
|
60
|
+
for (const n of Object.keys(r))
|
|
61
|
+
o.includes(n) || e(`${t}.${n}`, "Unknown synthesis setting. Pass credentials to provider.init(), not the lesson.");
|
|
62
|
+
for (const n of ["modelId", "languageCode"])
|
|
63
|
+
r[n] !== void 0 && !h(r[n]) && e(`${t}.${n}`, "Must be a nonempty string.");
|
|
64
|
+
for (const n of ["previousText", "nextText"])
|
|
65
|
+
r[n] !== void 0 && typeof r[n] != "string" && e(`${t}.${n}`, "Must be a string.");
|
|
66
|
+
r.seed !== void 0 && (!Number.isInteger(r.seed) || r.seed < 0 || r.seed > 4294967295) && e(`${t}.seed`, "Must be an integer from 0 to 4294967295."), r.applyTextNormalization !== void 0 && !["auto", "on", "off"].includes(r.applyTextNormalization) && e(`${t}.applyTextNormalization`, "Choose auto, on, or off.");
|
|
67
|
+
const a = r.voiceSettings;
|
|
68
|
+
if (a !== void 0)
|
|
69
|
+
if (!a || typeof a != "object" || Array.isArray(a)) e(`${t}.voiceSettings`, "Expected a settings object.");
|
|
70
|
+
else for (const [n, m] of Object.entries(a))
|
|
71
|
+
n === "useSpeakerBoost" ? typeof m != "boolean" && e(`${t}.voiceSettings.${n}`, "Must be a boolean.") : ["stability", "similarityBoost", "style", "speed"].includes(n) ? (!Number.isFinite(m) || m < (n === "speed" ? 0.7 : 0) || m > (n === "speed" ? 1.2 : 1)) && e(`${t}.voiceSettings.${n}`, n === "speed" ? "Must be between 0.7 and 1.2." : "Must be between 0 and 1.") : e(`${t}.voiceSettings.${n}`, "Unknown voice setting.");
|
|
72
|
+
r.pronunciationDictionaries !== void 0 && (!Array.isArray(r.pronunciationDictionaries) || r.pronunciationDictionaries.length > 3 || r.pronunciationDictionaries.some((n) => !n || !h(n.id) || n.versionId !== void 0 && !h(n.versionId))) && e(`${t}.pronunciationDictionaries`, "Provide at most three dictionary IDs with optional version IDs.");
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const c = /* @__PURE__ */ new Set();
|
|
76
|
+
Array.isArray(s.sources) ? s.sources.forEach((t, o) => {
|
|
77
|
+
!t || !y.test(t.id) || c.has(t.id) || !h(t.title) ? e(`sources[${o}]`, "Provide a unique source ID and title.") : c.add(t.id);
|
|
56
78
|
}) : e("sources", "Provide a sources array; it may be empty for an illustrative construction.");
|
|
57
|
-
const
|
|
79
|
+
const l = (t, o) => {
|
|
58
80
|
if (!Array.isArray(t))
|
|
59
|
-
e(
|
|
81
|
+
e(o, "Expected source IDs.");
|
|
60
82
|
else
|
|
61
|
-
for (const
|
|
62
|
-
|
|
63
|
-
},
|
|
64
|
-
if (
|
|
83
|
+
for (const a of t)
|
|
84
|
+
c.has(a) || e(o, `Unknown source '${a}'.`);
|
|
85
|
+
}, p = /* @__PURE__ */ new Set();
|
|
86
|
+
if (s.claims !== void 0 && !Array.isArray(s.claims))
|
|
65
87
|
e("claims", "Expected an array.");
|
|
66
88
|
else
|
|
67
|
-
for (const [t,
|
|
68
|
-
if (!
|
|
89
|
+
for (const [t, o] of (s.claims ?? []).entries()) {
|
|
90
|
+
if (!o || !y.test(o.id) || p.has(o.id) || !h(o.text)) {
|
|
69
91
|
e(`claims[${t}]`, "Provide a unique claim ID and text.");
|
|
70
92
|
continue;
|
|
71
93
|
}
|
|
72
|
-
|
|
94
|
+
p.add(o.id), l(o.sourceIds, `claims[${t}].sourceIds`), o.illustrative !== void 0 && typeof o.illustrative != "boolean" && e(`claims[${t}].illustrative`, "Must be a boolean."), !o.illustrative && !o.sourceIds?.length && e(`claims[${t}].sourceIds`, "Factual claims need a source; mark illustrative models explicitly.");
|
|
73
95
|
}
|
|
74
|
-
const
|
|
75
|
-
return !Array.isArray(
|
|
76
|
-
const
|
|
77
|
-
if (!t || !
|
|
78
|
-
e(
|
|
96
|
+
const v = /* @__PURE__ */ new Set();
|
|
97
|
+
return !Array.isArray(s.beats) || !s.beats.length ? (e("beats", "Provide at least one beat."), u) : (s.beats.forEach((t, o) => {
|
|
98
|
+
const a = `beats[${o}]`;
|
|
99
|
+
if (!t || !y.test(t.id) || v.has(t.id)) {
|
|
100
|
+
e(a, "Beat IDs must be unique lowercase identifiers.");
|
|
79
101
|
return;
|
|
80
102
|
}
|
|
81
|
-
|
|
82
|
-
for (const
|
|
83
|
-
|
|
84
|
-
t.minDurationMs !== void 0 && (!Number.isFinite(t.minDurationMs) || t.minDurationMs < 0) && e(`${
|
|
85
|
-
const
|
|
86
|
-
!Array.isArray(t.narration) || !t.narration.length ? e(`${
|
|
87
|
-
if (!
|
|
88
|
-
e(`${
|
|
103
|
+
v.add(t.id);
|
|
104
|
+
for (const f of ["goal", "visibleChange", "focus"])
|
|
105
|
+
h(t[f]) || e(`${a}.${f}`, "Describe the learning goal, visible event, and attention target.");
|
|
106
|
+
t.minDurationMs !== void 0 && (!Number.isFinite(t.minDurationMs) || t.minDurationMs < 0) && e(`${a}.minDurationMs`, "Must be nonnegative."), t.sourceIds !== void 0 && l(t.sourceIds, `${a}.sourceIds`);
|
|
107
|
+
const n = /* @__PURE__ */ new Map();
|
|
108
|
+
!Array.isArray(t.narration) || !t.narration.length ? e(`${a}.narration`, "Provide at least one spoken segment, also used for silent preview timing.") : t.narration.forEach((f, d) => {
|
|
109
|
+
if (!f || !y.test(f.id) || n.has(f.id) || !h(f.text)) {
|
|
110
|
+
e(`${a}.narration[${d}]`, "Provide a unique segment ID and nonempty spoken text.");
|
|
89
111
|
return;
|
|
90
112
|
}
|
|
91
|
-
|
|
113
|
+
n.set(f.id, f.text), f.speechText !== void 0 && !h(f.speechText) && e(`${a}.narration[${d}].speechText`, "Must be nonempty synthesis text."), f.pauseAfterMs !== void 0 && (!Number.isFinite(f.pauseAfterMs) || f.pauseAfterMs < 0) && e(`${a}.narration[${d}].pauseAfterMs`, "Must be nonnegative.");
|
|
92
114
|
});
|
|
93
115
|
const m = /* @__PURE__ */ new Set();
|
|
94
116
|
if (t.cues !== void 0 && !Array.isArray(t.cues))
|
|
95
|
-
e(`${
|
|
117
|
+
e(`${a}.cues`, "Expected an array.");
|
|
96
118
|
else
|
|
97
|
-
for (const [
|
|
98
|
-
if (!
|
|
99
|
-
e(`${
|
|
119
|
+
for (const [f, d] of (t.cues ?? []).entries()) {
|
|
120
|
+
if (!d || !y.test(d.id) || m.has(d.id)) {
|
|
121
|
+
e(`${a}.cues[${f}]`, "Provide a unique cue ID.");
|
|
100
122
|
continue;
|
|
101
123
|
}
|
|
102
|
-
m.add(
|
|
124
|
+
m.add(d.id), n.has(d.segment) || e(`${a}.cues[${f}].segment`, "Cue references an unknown narration segment."), d.phrase !== void 0 && (!h(d.phrase) || !g(n.get(d.segment) ?? "").includes(g(d.phrase))) && e(`${a}.cues[${f}].phrase`, "Phrase must occur in the specified spoken segment."), d.occurrence !== void 0 && (!Number.isSafeInteger(d.occurrence) || d.occurrence < 1) && e(`${a}.cues[${f}].occurrence`, "Must be a positive occurrence number."), d.offsetMs !== void 0 && (!Number.isFinite(d.offsetMs) || d.offsetMs < 0) && e(`${a}.cues[${f}].offsetMs`, "Must be nonnegative.");
|
|
103
125
|
}
|
|
104
|
-
}),
|
|
126
|
+
}), u);
|
|
105
127
|
}
|
|
106
|
-
function
|
|
107
|
-
const
|
|
108
|
-
if (
|
|
109
|
-
throw new Error(
|
|
128
|
+
function C(i) {
|
|
129
|
+
const u = D(i);
|
|
130
|
+
if (u.length)
|
|
131
|
+
throw new Error(u.map((e) => `${e.path}: ${e.message}`).join(`
|
|
110
132
|
`));
|
|
111
133
|
}
|
|
112
|
-
function
|
|
113
|
-
const e =
|
|
114
|
-
return
|
|
115
|
-
e.every((
|
|
116
|
-
}),
|
|
134
|
+
function N(i, u) {
|
|
135
|
+
const e = u.trim().split(/\s+/).map(g), s = [];
|
|
136
|
+
return i.forEach((r, c) => {
|
|
137
|
+
e.every((l, p) => g(i[c + p]?.text ?? "") === l) && s.push(c);
|
|
138
|
+
}), s;
|
|
117
139
|
}
|
|
118
|
-
function
|
|
119
|
-
if (
|
|
140
|
+
function j(i, u, e = 60) {
|
|
141
|
+
if (C(i), !Number.isSafeInteger(e) || e < 1 || e > 240)
|
|
120
142
|
throw new Error("fps must be an integer from 1 to 240.");
|
|
121
|
-
if (
|
|
143
|
+
if (i.narration.enabled && (!u || u.version !== 1 || !u.assets || typeof u.assets != "object" || u.voiceKey !== I(i)))
|
|
122
144
|
throw new Error("Narration is not prepared for these voice settings. Run retiam lesson prepare.");
|
|
123
|
-
let
|
|
124
|
-
const
|
|
125
|
-
for (const
|
|
126
|
-
let
|
|
127
|
-
const
|
|
128
|
-
for (const o of
|
|
129
|
-
const
|
|
130
|
-
if (
|
|
131
|
-
throw new Error(`Missing or stale audio: ${
|
|
132
|
-
const
|
|
133
|
-
if (!Number.isFinite(
|
|
134
|
-
throw new Error(`Invalid speech duration: ${
|
|
135
|
-
if (
|
|
136
|
-
throw new Error(`Missing word timings: ${
|
|
137
|
-
const
|
|
138
|
-
let
|
|
139
|
-
for (const
|
|
140
|
-
if (!
|
|
141
|
-
throw new Error(`Invalid word timings: ${
|
|
142
|
-
|
|
145
|
+
let s = 0;
|
|
146
|
+
const r = [];
|
|
147
|
+
for (const c of i.beats) {
|
|
148
|
+
let l = 0;
|
|
149
|
+
const p = [];
|
|
150
|
+
for (const o of c.narration) {
|
|
151
|
+
const a = i.narration.enabled ? u.assets[S(c.id, o.id)] : void 0;
|
|
152
|
+
if (i.narration.enabled && (!a || a.text !== o.text || (a.speechText ?? a.text) !== (o.speechText ?? o.text)))
|
|
153
|
+
throw new Error(`Missing or stale audio: ${c.id}/${o.id}. Prepare narration again.`);
|
|
154
|
+
const n = a?.durationMs ?? Math.max(900, o.text.trim().split(/\s+/).length * 360);
|
|
155
|
+
if (!Number.isFinite(n) || n <= 0)
|
|
156
|
+
throw new Error(`Invalid speech duration: ${c.id}/${o.id}.`);
|
|
157
|
+
if (a && (!Array.isArray(a.words) || !a.words.length))
|
|
158
|
+
throw new Error(`Missing word timings: ${c.id}/${o.id}.`);
|
|
159
|
+
const m = a?.words ?? o.text.trim().split(/\s+/).map((d, $, M) => ({ text: d, offsetMs: n * $ / M.length, durationMs: n / M.length }));
|
|
160
|
+
let f = -1;
|
|
161
|
+
for (const d of m) {
|
|
162
|
+
if (!h(d.text) || !Number.isFinite(d.offsetMs) || !Number.isFinite(d.durationMs) || d.offsetMs < f - 0.1 || d.durationMs < 0 || d.offsetMs + d.durationMs > n + 1)
|
|
163
|
+
throw new Error(`Invalid word timings: ${c.id}/${o.id}.`);
|
|
164
|
+
f = d.offsetMs + d.durationMs;
|
|
143
165
|
}
|
|
144
|
-
|
|
166
|
+
p.push({ ...a, text: o.text, id: o.id, durationMs: n, words: m, startMs: l, pauseAfterMs: o.pauseAfterMs ?? 0 }), l += n + (o.pauseAfterMs ?? 0);
|
|
145
167
|
}
|
|
146
|
-
const
|
|
147
|
-
for (const o of
|
|
148
|
-
const
|
|
149
|
-
let
|
|
168
|
+
const v = Math.ceil(Math.max(c.minDurationMs ?? 0, l + 450) * e / 1e3 - 1e-8) * 1e3 / e, t = {};
|
|
169
|
+
for (const o of c.cues ?? []) {
|
|
170
|
+
const a = p.find((m) => m.id === o.segment);
|
|
171
|
+
let n = a.startMs;
|
|
150
172
|
if (o.phrase) {
|
|
151
|
-
const
|
|
152
|
-
if (
|
|
153
|
-
throw new Error(`Ambiguous cue '${
|
|
154
|
-
const
|
|
155
|
-
if (
|
|
156
|
-
throw new Error(`Unaligned cue '${
|
|
157
|
-
|
|
173
|
+
const m = N(a.words, o.phrase);
|
|
174
|
+
if (m.length > 1 && o.occurrence === void 0)
|
|
175
|
+
throw new Error(`Ambiguous cue '${c.id}/${o.id}'; set occurrence.`);
|
|
176
|
+
const f = m[(o.occurrence ?? 1) - 1];
|
|
177
|
+
if (f === void 0)
|
|
178
|
+
throw new Error(`Unaligned cue '${c.id}/${o.id}': ${o.phrase}.`);
|
|
179
|
+
n += a.words[f].offsetMs;
|
|
158
180
|
}
|
|
159
|
-
if (
|
|
160
|
-
throw new Error(`Cue '${
|
|
161
|
-
|
|
181
|
+
if (n += o.offsetMs ?? 0, n > a.startMs + a.durationMs)
|
|
182
|
+
throw new Error(`Cue '${c.id}/${o.id}' falls outside its speech segment.`);
|
|
183
|
+
t[o.id] = n;
|
|
162
184
|
}
|
|
163
|
-
|
|
185
|
+
r.push({ id: c.id, startMs: s, durationMs: v, segments: p, cues: t }), s += v;
|
|
164
186
|
}
|
|
165
|
-
return { version: 1, timing:
|
|
187
|
+
return { version: 1, timing: i.narration.enabled ? "recorded" : "estimated", fps: e, durationMs: s, beats: r };
|
|
166
188
|
}
|
|
167
|
-
function O(
|
|
168
|
-
if (!Number.isInteger(
|
|
189
|
+
function O(i, u = 8) {
|
|
190
|
+
if (!Number.isInteger(u) || u < 1)
|
|
169
191
|
throw new Error("wordsPerCue must be positive.");
|
|
170
|
-
const e = (
|
|
171
|
-
const
|
|
172
|
-
return `${String(Math.floor(
|
|
192
|
+
const e = (c) => {
|
|
193
|
+
const l = Math.round(c);
|
|
194
|
+
return `${String(Math.floor(l / 36e5)).padStart(2, "0")}:${String(Math.floor(l / 6e4) % 60).padStart(2, "0")}:${String(Math.floor(l / 1e3) % 60).padStart(2, "0")},${String(l % 1e3).padStart(3, "0")}`;
|
|
173
195
|
};
|
|
174
|
-
let
|
|
175
|
-
const
|
|
176
|
-
for (const
|
|
177
|
-
for (const
|
|
178
|
-
for (let
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
${e(
|
|
182
|
-
${
|
|
196
|
+
let s = 0;
|
|
197
|
+
const r = [];
|
|
198
|
+
for (const c of i.beats)
|
|
199
|
+
for (const l of c.segments)
|
|
200
|
+
for (let p = 0; p < l.words.length; p += u) {
|
|
201
|
+
const v = l.words.slice(p, p + u), t = v.at(-1);
|
|
202
|
+
r.push(`${++s}
|
|
203
|
+
${e(c.startMs + l.startMs + v[0].offsetMs)} --> ${e(c.startMs + l.startMs + t.offsetMs + t.durationMs)}
|
|
204
|
+
${v.map((o) => o.text).join(" ")}
|
|
183
205
|
`);
|
|
184
206
|
}
|
|
185
|
-
return
|
|
207
|
+
return r.join(`
|
|
186
208
|
`);
|
|
187
209
|
}
|
|
188
210
|
export {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
211
|
+
b as STYLE_PROFILES,
|
|
212
|
+
C as assertLesson,
|
|
213
|
+
j as compileLesson,
|
|
192
214
|
O as lessonCaptions,
|
|
193
|
-
|
|
215
|
+
F as resolveStyle,
|
|
194
216
|
S as speechKey,
|
|
195
|
-
|
|
217
|
+
D as validateLesson,
|
|
196
218
|
I as voiceKey
|
|
197
219
|
};
|