@jokerized/decksmith 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/README.md +794 -0
- package/dist/cli.js +8906 -0
- package/dist/deck-runtime.js +55 -0
- package/dist/index.js +8590 -0
- package/dist/mcp.js +7809 -0
- package/dist/server/errors.js +73 -0
- package/dist/server/http.js +504 -0
- package/dist/server/main.js +91 -0
- package/dist/server/options.js +198 -0
- package/dist/server/pipeline.js +356 -0
- package/dist/server/queue.js +195 -0
- package/dist/server/ui.js +1614 -0
- package/dist/server/upload.js +232 -0
- package/dist/types/cli.d.ts +1 -0
- package/dist/types/deck/runtime.d.ts +59 -0
- package/dist/types/deck/subtitles.d.ts +101 -0
- package/dist/types/emit/archetypes/annotated-figure.d.ts +103 -0
- package/dist/types/emit/archetypes/bar-compare.d.ts +30 -0
- package/dist/types/emit/archetypes/callout.d.ts +7 -0
- package/dist/types/emit/archetypes/claim-figure.d.ts +9 -0
- package/dist/types/emit/archetypes/data-table.d.ts +21 -0
- package/dist/types/emit/archetypes/equation-walk.d.ts +2 -0
- package/dist/types/emit/archetypes/grid.d.ts +16 -0
- package/dist/types/emit/archetypes/index.d.ts +19 -0
- package/dist/types/emit/archetypes/line-chart.d.ts +22 -0
- package/dist/types/emit/archetypes/pipeline.d.ts +81 -0
- package/dist/types/emit/archetypes/split-compare.d.ts +2 -0
- package/dist/types/emit/archetypes/stack.d.ts +93 -0
- package/dist/types/emit/archetypes/title.d.ts +188 -0
- package/dist/types/emit/camera.d.ts +397 -0
- package/dist/types/emit/composition.d.ts +191 -0
- package/dist/types/emit/island.d.ts +19 -0
- package/dist/types/emit/kit.d.ts +256 -0
- package/dist/types/emit/svg.d.ts +177 -0
- package/dist/types/emit/theme.d.ts +65 -0
- package/dist/types/emit/themes/index.d.ts +40 -0
- package/dist/types/emit/themes/ink.d.ts +12 -0
- package/dist/types/emit/themes/mono.d.ts +20 -0
- package/dist/types/emit/themes/paper.d.ts +18 -0
- package/dist/types/index.d.ts +200 -0
- package/dist/types/mcp/main.d.ts +2 -0
- package/dist/types/mcp/prereqs.d.ts +22 -0
- package/dist/types/mcp/tools.d.ts +212 -0
- package/dist/types/narrate/narrate.d.ts +87 -0
- package/dist/types/narrate/tts.d.ts +134 -0
- package/dist/types/narrate/voices.d.ts +29 -0
- package/dist/types/pack/media.d.ts +61 -0
- package/dist/types/pack/pack.d.ts +16 -0
- package/dist/types/plan/codex.d.ts +24 -0
- package/dist/types/plan/duration.d.ts +394 -0
- package/dist/types/plan/prompt.d.ts +47 -0
- package/dist/types/plan/refs.d.ts +22 -0
- package/dist/types/plan/select.d.ts +116 -0
- package/dist/types/prefs.d.ts +43 -0
- package/dist/types/render/captions.d.ts +108 -0
- package/dist/types/render/ffmpeg.d.ts +129 -0
- package/dist/types/render/render.d.ts +123 -0
- package/dist/types/render/timing.d.ts +290 -0
- package/dist/types/server/errors.d.ts +11 -0
- package/dist/types/server/http.d.ts +57 -0
- package/dist/types/server/main.d.ts +1 -0
- package/dist/types/server/options.d.ts +90 -0
- package/dist/types/server/pipeline.d.ts +21 -0
- package/dist/types/server/queue.d.ts +105 -0
- package/dist/types/server/ui.d.ts +9 -0
- package/dist/types/server/upload.d.ts +107 -0
- package/dist/types/source/assets.d.ts +11 -0
- package/dist/types/source/fonts.d.ts +15 -0
- package/dist/types/source/markdown.d.ts +8 -0
- package/dist/types/types.d.ts +1992 -0
- package/dist/types/verify/budget.d.ts +41 -0
- package/dist/types/verify/check.d.ts +78 -0
- package/dist/types/verify/drift.d.ts +158 -0
- package/dist/types/verify/fidelity.d.ts +247 -0
- package/dist/types/verify/index.d.ts +207 -0
- package/dist/types/verify/overprint.d.ts +133 -0
- package/dist/types/verify/typefloor.d.ts +50 -0
- package/package.json +84 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { type DeckNarration } from "./emit/composition.js";
|
|
2
|
+
import type { Cut } from "./plan/select.js";
|
|
3
|
+
import { type Format, type Source, type Storyboard } from "./types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Markdown (and LaTeX-ish markdown) in, typed `Source` out. The one parser we
|
|
6
|
+
* have; a consumer that already holds structured text can skip it and hand
|
|
7
|
+
* `sourceSchema.parse(...)` straight to the planner.
|
|
8
|
+
*/
|
|
9
|
+
export { parseMarkdown } from "./source/markdown.js";
|
|
10
|
+
export type { ParseOptions } from "./source/markdown.js";
|
|
11
|
+
/**
|
|
12
|
+
* Figures referenced by URL, downloaded beside the source. Separate from
|
|
13
|
+
* `parseMarkdown` because it touches the network and a server may want to fetch
|
|
14
|
+
* through its own client instead.
|
|
15
|
+
*/
|
|
16
|
+
export { fetchFigures } from "./source/assets.js";
|
|
17
|
+
/**
|
|
18
|
+
* Subset a CJK webfont over the glyphs a deck actually renders. Exported
|
|
19
|
+
* because invariant 9 — a font stack naming a family the bundle does not
|
|
20
|
+
* declare falls back silently — is not something a consumer can debug from the
|
|
21
|
+
* outside, and `buildDeck` calls it for you only for its own output directory.
|
|
22
|
+
*/
|
|
23
|
+
export { bundleFont } from "./source/fonts.js";
|
|
24
|
+
export type { FontBundle } from "./source/fonts.js";
|
|
25
|
+
/**
|
|
26
|
+
* The planner. `Runner` is exported with it on purpose: `codexPlanner` shells
|
|
27
|
+
* to the Codex CLI by default, which is wrong for a server, and substituting a
|
|
28
|
+
* runner is the supported way to drive it from an SDK instead of a subprocess.
|
|
29
|
+
*/
|
|
30
|
+
export { codexPlanner } from "./plan/codex.js";
|
|
31
|
+
export type { CodexOptions, Runner } from "./plan/codex.js";
|
|
32
|
+
/**
|
|
33
|
+
* Fails loudly when a beat cites a figure, table or equation the source does
|
|
34
|
+
* not contain. `buildDeck` does not call it — a caller assembling a storyboard
|
|
35
|
+
* by hand wants this before it spends a render on a dangling ref.
|
|
36
|
+
*/
|
|
37
|
+
export { assertInsideResolves, assertRefsResolve } from "./plan/refs.js";
|
|
38
|
+
/** The prompt, so a consumer driving its own model can reproduce our planning. */
|
|
39
|
+
export { renderSource, systemPrompt } from "./plan/prompt.js";
|
|
40
|
+
/**
|
|
41
|
+
* The three exposed length knobs — `duration`, `slides`, `narration.density` —
|
|
42
|
+
* turned into the two derived ones. Pure arithmetic over preferences, so a
|
|
43
|
+
* caller can ask what a target costs before spending a plan on it.
|
|
44
|
+
*/
|
|
45
|
+
export { COMFORTABLE_CPS, durationPlan, slidesFor, LAST_HOLD_SECONDS, MAX_PLAYBACK, MIN_SENTENCE_CHARS, MOTION_SHARE, p95CueRate, playbackFactor, playbackWarning, SPEAKING_STOPS, SPEECH_CPS, STOPS_PER_BEAT, tempoChain, } from "./plan/duration.js";
|
|
46
|
+
export type { DurationPlan } from "./plan/duration.js";
|
|
47
|
+
/**
|
|
48
|
+
* Text-to-speech over a storyboard. Needs the `edge-tts` binary on PATH, which
|
|
49
|
+
* is why it is a separate call and not a `buildDeck` option.
|
|
50
|
+
*/
|
|
51
|
+
export { narrate } from "./narrate/narrate.js";
|
|
52
|
+
export type { NarrateOpts } from "./narrate/narrate.js";
|
|
53
|
+
export { narratableLangs, pickVoice } from "./narrate/voices.js";
|
|
54
|
+
/**
|
|
55
|
+
* The synthesiser, behind an interface.
|
|
56
|
+
*
|
|
57
|
+
* edge-tts is an unofficial client of Edge's Read Aloud endpoint — no terms, no
|
|
58
|
+
* SLA, and a signed-token scheme that has broken clients before. So it is one
|
|
59
|
+
* implementation of `SpeechProvider` rather than the shape of the code: hand
|
|
60
|
+
* `synthesize` a provider and nothing else here knows the difference. Register
|
|
61
|
+
* a new one in `PROVIDERS`, or select at runtime with DECKSMITH_TTS.
|
|
62
|
+
*/
|
|
63
|
+
export { edgeProvider, parseCues, PROVIDERS, resolveProvider, synthesize, } from "./narrate/tts.js";
|
|
64
|
+
export type { SpeechProvider, SpeechRequest, SpeechResult, SynthOpts, } from "./narrate/tts.js";
|
|
65
|
+
/**
|
|
66
|
+
* The pure core: storyboard + source + format → HTML strings, no I/O. Exported
|
|
67
|
+
* beneath `buildDeck` for callers that write to something other than a
|
|
68
|
+
* filesystem — object storage, a response body, a test.
|
|
69
|
+
*/
|
|
70
|
+
export { DECK_PAGE, emitComposition, emitDeck, planCut, PLAYER_FILE } from "./emit/composition.js";
|
|
71
|
+
export type { Deck, DeckNarration, DeckOptions } from "./emit/composition.js";
|
|
72
|
+
/**
|
|
73
|
+
* Which beats a format keeps and why the rest went. `emitDeck` returns one on
|
|
74
|
+
* every build; `planCut` answers the same question without emitting a deck, for
|
|
75
|
+
* a caller costing a cut before committing to it. `selectBeats` is the rule
|
|
76
|
+
* itself, exported so it can be run against a budget no `Format` states.
|
|
77
|
+
*/
|
|
78
|
+
export { selectBeats } from "./plan/select.js";
|
|
79
|
+
export type { Cut, Dangling, Dropped, DropRule, SelectionBudget } from "./plan/select.js";
|
|
80
|
+
/** Themes are a named, closed set; a consumer needs to enumerate and validate. */
|
|
81
|
+
export { resolveTheme, THEME_NAMES, THEMES } from "./emit/themes/index.js";
|
|
82
|
+
export type { DeckTheme } from "./emit/themes/index.js";
|
|
83
|
+
/**
|
|
84
|
+
* The gates. `verify` is ours plus HyperFrames'; `check` is HyperFrames' alone
|
|
85
|
+
* and needs its CLI. A build that never opens a browser can still run `verify`
|
|
86
|
+
* for the determinism and narration scans, which are pure string work.
|
|
87
|
+
*/
|
|
88
|
+
export { verify } from "./verify/index.js";
|
|
89
|
+
/**
|
|
90
|
+
* The storyboard advisory `plan` prints and `verify` folds in: a headline that
|
|
91
|
+
* recites the visual's own labels as a list. Exported because it is pure over a
|
|
92
|
+
* storyboard — no browser, no built deck — so a caller can run it on a plan
|
|
93
|
+
* before spending anything on it, which is the only moment it is worth acting on.
|
|
94
|
+
*/
|
|
95
|
+
export { scanHeadlines, scanNarrationLead, scanRepeatedObject } from "./verify/index.js";
|
|
96
|
+
export { check, parseCheckReport, sampleTimes } from "./verify/check.js";
|
|
97
|
+
export type { CheckOptions } from "./verify/check.js";
|
|
98
|
+
/**
|
|
99
|
+
* The chart-collision rule. Exported for the same reason `inkBelow` is: the
|
|
100
|
+
* predicate is pure over a list of measured text runs, so what counts as a
|
|
101
|
+
* collision is something a caller can read and test rather than infer from a
|
|
102
|
+
* verdict. `verify` runs it; nothing else has to.
|
|
103
|
+
*/
|
|
104
|
+
export { gradeOverprint, MIN_OVERLAP, overprints } from "./verify/overprint.js";
|
|
105
|
+
export type { Overprint, Overprinted, TextRun } from "./verify/overprint.js";
|
|
106
|
+
/**
|
|
107
|
+
* The determinism gate: render the deck twice and compare. Separate from
|
|
108
|
+
* `verify` because it costs two full renders — minutes, not milliseconds — so
|
|
109
|
+
* it is a thing you schedule, not a thing you run on a request path. `identical`
|
|
110
|
+
* is only honest on an image-free deck with no camera; see
|
|
111
|
+
* `demo/fixtures/plain.storyboard.json` and .planning/EXPERIMENT-006.
|
|
112
|
+
*/
|
|
113
|
+
export { drift, FLOOR_DB } from "./verify/index.js";
|
|
114
|
+
export type { DriftMode, DriftOptions, DriftReport } from "./verify/index.js";
|
|
115
|
+
/**
|
|
116
|
+
* Deck directory in, mp4 out. Needs Chrome and ffmpeg, so — like `verify` —
|
|
117
|
+
* this is the batch half of the library, not the request path. `planTiming` and
|
|
118
|
+
* `framePlan` are exported beside it because they are pure arithmetic: a caller
|
|
119
|
+
* sizing a job, or costing a render before paying for it, wants the frame count
|
|
120
|
+
* without spawning anything.
|
|
121
|
+
*/
|
|
122
|
+
export { render } from "./render/render.js";
|
|
123
|
+
export type { RenderOptions, RenderResult, SubtitleMode } from "./render/render.js";
|
|
124
|
+
export { framePlan, planTiming, TIMING_FILE, toSrt } from "./render/timing.js";
|
|
125
|
+
export type { FramePlan, Timing, TimingInput, TimedScene, TimedSegment } from "./render/timing.js";
|
|
126
|
+
/** The .deck container. Read and write, because a server does both. */
|
|
127
|
+
export { openPack, readPack, writePack } from "./pack/pack.js";
|
|
128
|
+
export type { Pack, PackFiles } from "./pack/pack.js";
|
|
129
|
+
export { mediaSummary, planMedia } from "./pack/media.js";
|
|
130
|
+
export type { AssetRequest, Fetcher, Media, MediaPlan } from "./pack/media.js";
|
|
131
|
+
/**
|
|
132
|
+
* `decksmith.config.json` and its defaults. `prefsFromFlags` is deliberately
|
|
133
|
+
* NOT here — it translates commander's flag object, which is the CLI's problem.
|
|
134
|
+
*/
|
|
135
|
+
export { CONFIG_FILE, loadPrefs } from "./prefs.js";
|
|
136
|
+
export type { Prefs, PrefsPatch } from "./prefs.js";
|
|
137
|
+
/**
|
|
138
|
+
* Every schema and type. Unusually broad for this file, and justified: `types.ts`
|
|
139
|
+
* is nothing but the wire format — what `source.json`, `storyboard.json`,
|
|
140
|
+
* `narration.json` and `.deck` already are on disk. It is a published contract
|
|
141
|
+
* whether or not this file re-exports it, so hiding half of it would only make
|
|
142
|
+
* consumers restate the halves they need.
|
|
143
|
+
*/
|
|
144
|
+
export * from "./types.js";
|
|
145
|
+
/** Where the emitted deck's parts came from and where they went. */
|
|
146
|
+
export interface BuildResult {
|
|
147
|
+
/** Absolute path of the directory written. */
|
|
148
|
+
out: string;
|
|
149
|
+
/** `deck.html` was written — true for navigable formats only. */
|
|
150
|
+
navigable: boolean;
|
|
151
|
+
/** Every path written, absolute, for a caller that wants to upload them. */
|
|
152
|
+
files: string[];
|
|
153
|
+
/**
|
|
154
|
+
* Which beats were drawn and why the rest were not.
|
|
155
|
+
*
|
|
156
|
+
* A caller with no terminal — a server, a queue worker — gets the same answer
|
|
157
|
+
* `build` prints, because "the short came out three minutes" is not a useful
|
|
158
|
+
* thing to learn without "and here is what it cost".
|
|
159
|
+
*/
|
|
160
|
+
cut: Cut;
|
|
161
|
+
}
|
|
162
|
+
export interface BuildDeckOptions {
|
|
163
|
+
/**
|
|
164
|
+
* Called when one beat cannot be drawn, instead of failing the whole build.
|
|
165
|
+
* Absent, the emitter's error propagates — see `DeckOptions.onBeatError`.
|
|
166
|
+
*/
|
|
167
|
+
onBeatError?: (beatId: string, err: Error) => void;
|
|
168
|
+
/** Any name in `THEME_NAMES`. Overrides `storyboard.theme`. */
|
|
169
|
+
theme?: string;
|
|
170
|
+
/** Multiplies every duration and hold. 1 leaves the bytes untouched. */
|
|
171
|
+
speed?: number;
|
|
172
|
+
/** Default `FORMATS["deck-16x9"]`. */
|
|
173
|
+
format?: Format;
|
|
174
|
+
/** From `narrate`. `dir` is where the mp3s sit relative to `deck.html`. */
|
|
175
|
+
narration?: DeckNarration;
|
|
176
|
+
/** Directory holding those mp3s now; they are copied into the deck. */
|
|
177
|
+
audioFrom?: string;
|
|
178
|
+
/** Directory whose `assets/` is copied in. Defaults to `out`'s neighbours. */
|
|
179
|
+
assetsFrom?: string;
|
|
180
|
+
/** Progress lines. Silent by default: a library that prints is a library you
|
|
181
|
+
* cannot run inside a request handler. */
|
|
182
|
+
onStep?: (message: string) => void;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* The `build` verb, minus argv: emit the deck and write everything it needs to
|
|
186
|
+
* open with no network — the composition, the navigable page, the player, the
|
|
187
|
+
* KaTeX CSS and fonts, the source's assets, the narration audio, and a refreshed
|
|
188
|
+
* font subset.
|
|
189
|
+
*
|
|
190
|
+
* It does NOT run the gates. `verify` needs the HyperFrames CLI and a browser,
|
|
191
|
+
* which a caller may not have and may not want on the request path; call it
|
|
192
|
+
* yourself when you do.
|
|
193
|
+
*
|
|
194
|
+
* NOTE: this is the same sequence as `build` in src/cli.ts, written out a second
|
|
195
|
+
* time rather than shared, because that file is owned elsewhere this pass. The
|
|
196
|
+
* next person to touch cli.ts should delete its copy and call this — two
|
|
197
|
+
* implementations of "what a deck directory contains" is exactly the kind of
|
|
198
|
+
* drift that ends in a deck that opens locally and 404s in production.
|
|
199
|
+
*/
|
|
200
|
+
export declare function buildDeck(storyboard: Storyboard, source: Source, outDir: string, opts?: BuildDeckOptions): Promise<BuildResult>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface Prereq {
|
|
2
|
+
name: string;
|
|
3
|
+
ok: boolean;
|
|
4
|
+
/** The stage that cannot run without it. */
|
|
5
|
+
neededFor: string;
|
|
6
|
+
/** What to type. Named because "install ffmpeg" is not an instruction. */
|
|
7
|
+
install: string;
|
|
8
|
+
/** Version string when it answered, so a wrong-version bug is visible. */
|
|
9
|
+
version?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Chrome is the odd one out: `hyperframes check` and the renderer each resolve
|
|
13
|
+
* their OWN browser, so probing one binary cannot prove either will find it.
|
|
14
|
+
* `npx puppeteer browsers install chrome` is what fixes it either way, and the
|
|
15
|
+
* probe is therefore advisory — hence the honest `neededFor` naming both stages.
|
|
16
|
+
*/
|
|
17
|
+
export declare function prereqs(): Promise<Prereq[]>;
|
|
18
|
+
/** The ones a job with these options actually needs, and which are missing. */
|
|
19
|
+
export declare function missingFor(found: readonly Prereq[], opts: {
|
|
20
|
+
narrate: boolean;
|
|
21
|
+
video: boolean;
|
|
22
|
+
}): Prereq[];
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { type Prereq } from "./prereqs.js";
|
|
3
|
+
/**
|
|
4
|
+
* The settings surface, mirroring `prefsSchema` and validated by it downstream.
|
|
5
|
+
*
|
|
6
|
+
* Every field optional and NONE with a default — see the header. The
|
|
7
|
+
* descriptions carry what six sessions of measurement learned, because a tool
|
|
8
|
+
* schema is the only documentation an agent reads.
|
|
9
|
+
*/
|
|
10
|
+
export declare const settingsSchema: z.ZodObject<{
|
|
11
|
+
format: z.ZodOptional<z.ZodEnum<{
|
|
12
|
+
[x: string]: string;
|
|
13
|
+
}>>;
|
|
14
|
+
theme: z.ZodOptional<z.ZodEnum<{
|
|
15
|
+
[x: string]: string;
|
|
16
|
+
}>>;
|
|
17
|
+
lang: z.ZodOptional<z.ZodString>;
|
|
18
|
+
tone: z.ZodOptional<z.ZodEnum<{
|
|
19
|
+
plain: "plain";
|
|
20
|
+
academic: "academic";
|
|
21
|
+
conversational: "conversational";
|
|
22
|
+
punchy: "punchy";
|
|
23
|
+
}>>;
|
|
24
|
+
density: z.ZodOptional<z.ZodEnum<{
|
|
25
|
+
sparse: "sparse";
|
|
26
|
+
normal: "normal";
|
|
27
|
+
dense: "dense";
|
|
28
|
+
}>>;
|
|
29
|
+
duration: z.ZodOptional<z.ZodNumber>;
|
|
30
|
+
slides: z.ZodOptional<z.ZodInt>;
|
|
31
|
+
animation_speed: z.ZodOptional<z.ZodNumber>;
|
|
32
|
+
narrate: z.ZodOptional<z.ZodBoolean>;
|
|
33
|
+
voice: z.ZodOptional<z.ZodString>;
|
|
34
|
+
rate: z.ZodOptional<z.ZodString>;
|
|
35
|
+
pitch: z.ZodOptional<z.ZodString>;
|
|
36
|
+
narration_density: z.ZodOptional<z.ZodEnum<{
|
|
37
|
+
high: "high";
|
|
38
|
+
medium: "medium";
|
|
39
|
+
low: "low";
|
|
40
|
+
}>>;
|
|
41
|
+
video: z.ZodOptional<z.ZodBoolean>;
|
|
42
|
+
}, z.core.$strip>;
|
|
43
|
+
export type Settings = z.infer<typeof settingsSchema>;
|
|
44
|
+
export declare const capabilitiesSchema: z.ZodObject<{}, z.core.$strip>;
|
|
45
|
+
export declare const estimateSchema: z.ZodObject<{
|
|
46
|
+
settings: z.ZodObject<{
|
|
47
|
+
format: z.ZodOptional<z.ZodEnum<{
|
|
48
|
+
[x: string]: string;
|
|
49
|
+
}>>;
|
|
50
|
+
theme: z.ZodOptional<z.ZodEnum<{
|
|
51
|
+
[x: string]: string;
|
|
52
|
+
}>>;
|
|
53
|
+
lang: z.ZodOptional<z.ZodString>;
|
|
54
|
+
tone: z.ZodOptional<z.ZodEnum<{
|
|
55
|
+
plain: "plain";
|
|
56
|
+
academic: "academic";
|
|
57
|
+
conversational: "conversational";
|
|
58
|
+
punchy: "punchy";
|
|
59
|
+
}>>;
|
|
60
|
+
density: z.ZodOptional<z.ZodEnum<{
|
|
61
|
+
sparse: "sparse";
|
|
62
|
+
normal: "normal";
|
|
63
|
+
dense: "dense";
|
|
64
|
+
}>>;
|
|
65
|
+
duration: z.ZodOptional<z.ZodNumber>;
|
|
66
|
+
slides: z.ZodOptional<z.ZodInt>;
|
|
67
|
+
animation_speed: z.ZodOptional<z.ZodNumber>;
|
|
68
|
+
narrate: z.ZodOptional<z.ZodBoolean>;
|
|
69
|
+
voice: z.ZodOptional<z.ZodString>;
|
|
70
|
+
rate: z.ZodOptional<z.ZodString>;
|
|
71
|
+
pitch: z.ZodOptional<z.ZodString>;
|
|
72
|
+
narration_density: z.ZodOptional<z.ZodEnum<{
|
|
73
|
+
high: "high";
|
|
74
|
+
medium: "medium";
|
|
75
|
+
low: "low";
|
|
76
|
+
}>>;
|
|
77
|
+
video: z.ZodOptional<z.ZodBoolean>;
|
|
78
|
+
}, z.core.$strip>;
|
|
79
|
+
}, z.core.$strip>;
|
|
80
|
+
export declare const createSchema: z.ZodObject<{
|
|
81
|
+
document_path: z.ZodOptional<z.ZodString>;
|
|
82
|
+
document_text: z.ZodOptional<z.ZodString>;
|
|
83
|
+
settings: z.ZodOptional<z.ZodObject<{
|
|
84
|
+
format: z.ZodOptional<z.ZodEnum<{
|
|
85
|
+
[x: string]: string;
|
|
86
|
+
}>>;
|
|
87
|
+
theme: z.ZodOptional<z.ZodEnum<{
|
|
88
|
+
[x: string]: string;
|
|
89
|
+
}>>;
|
|
90
|
+
lang: z.ZodOptional<z.ZodString>;
|
|
91
|
+
tone: z.ZodOptional<z.ZodEnum<{
|
|
92
|
+
plain: "plain";
|
|
93
|
+
academic: "academic";
|
|
94
|
+
conversational: "conversational";
|
|
95
|
+
punchy: "punchy";
|
|
96
|
+
}>>;
|
|
97
|
+
density: z.ZodOptional<z.ZodEnum<{
|
|
98
|
+
sparse: "sparse";
|
|
99
|
+
normal: "normal";
|
|
100
|
+
dense: "dense";
|
|
101
|
+
}>>;
|
|
102
|
+
duration: z.ZodOptional<z.ZodNumber>;
|
|
103
|
+
slides: z.ZodOptional<z.ZodInt>;
|
|
104
|
+
animation_speed: z.ZodOptional<z.ZodNumber>;
|
|
105
|
+
narrate: z.ZodOptional<z.ZodBoolean>;
|
|
106
|
+
voice: z.ZodOptional<z.ZodString>;
|
|
107
|
+
rate: z.ZodOptional<z.ZodString>;
|
|
108
|
+
pitch: z.ZodOptional<z.ZodString>;
|
|
109
|
+
narration_density: z.ZodOptional<z.ZodEnum<{
|
|
110
|
+
high: "high";
|
|
111
|
+
medium: "medium";
|
|
112
|
+
low: "low";
|
|
113
|
+
}>>;
|
|
114
|
+
video: z.ZodOptional<z.ZodBoolean>;
|
|
115
|
+
}, z.core.$strip>>;
|
|
116
|
+
wait_seconds: z.ZodOptional<z.ZodNumber>;
|
|
117
|
+
}, z.core.$strip>;
|
|
118
|
+
export declare const statusSchema: z.ZodObject<{
|
|
119
|
+
job_id: z.ZodString;
|
|
120
|
+
wait_seconds: z.ZodOptional<z.ZodNumber>;
|
|
121
|
+
}, z.core.$strip>;
|
|
122
|
+
export interface McpOptions {
|
|
123
|
+
/** Documents must live under here. Nothing outside is readable. */
|
|
124
|
+
root: string;
|
|
125
|
+
/** Where jobs write. Defaults under the OS temp dir. */
|
|
126
|
+
work: string;
|
|
127
|
+
/** Injected in tests so no test reaches the network or the clock. */
|
|
128
|
+
now?: () => number;
|
|
129
|
+
/**
|
|
130
|
+
* Injected for the same reason as `now`: which binaries a machine happens to
|
|
131
|
+
* have is not what a test is about, and CI has no Codex. Defaults to the real
|
|
132
|
+
* probe, so the server itself is unchanged.
|
|
133
|
+
*/
|
|
134
|
+
probe?: () => Promise<Prereq[]>;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* The four tools, over one queue.
|
|
138
|
+
*
|
|
139
|
+
* Returned as plain functions rather than registered against a transport so the
|
|
140
|
+
* tests can call them directly — the protocol is `main.ts`'s business and every
|
|
141
|
+
* behaviour worth checking is here.
|
|
142
|
+
*/
|
|
143
|
+
export declare function deckTools(opts: McpOptions): {
|
|
144
|
+
/** Formats, themes, every setting's range, and what is actually installed. */
|
|
145
|
+
capabilities(): Promise<{
|
|
146
|
+
prerequisites: Prereq[];
|
|
147
|
+
root: string;
|
|
148
|
+
work: string;
|
|
149
|
+
}>;
|
|
150
|
+
/**
|
|
151
|
+
* What a duration/slides/density combination costs, before a minute of Codex
|
|
152
|
+
* is spent finding out. Pure arithmetic — no job, no browser, instant.
|
|
153
|
+
*
|
|
154
|
+
* Returns `durationPlan`'s warnings VERBATIM. They are the product telling
|
|
155
|
+
* the author what their settings cost ("60s over 12 slides leaves 85
|
|
156
|
+
* characters a slide, which is one sentence"), and an MCP that swallowed
|
|
157
|
+
* them would ship the exact failure this project keeps having.
|
|
158
|
+
*/
|
|
159
|
+
estimate(input: z.infer<typeof estimateSchema>): {
|
|
160
|
+
slides: number;
|
|
161
|
+
format: string;
|
|
162
|
+
seconds_per_slide: number | undefined;
|
|
163
|
+
animation_speed: number;
|
|
164
|
+
narration_rate: string;
|
|
165
|
+
characters_per_slide: number | undefined;
|
|
166
|
+
sentences_per_slide: number | undefined;
|
|
167
|
+
speech_seconds_per_slide: number | undefined;
|
|
168
|
+
warnings: string[];
|
|
169
|
+
};
|
|
170
|
+
/** Submit a document, wait a bounded slice, report. */
|
|
171
|
+
create(input: z.infer<typeof createSchema>): Promise<{
|
|
172
|
+
next: string;
|
|
173
|
+
deck_path?: string | undefined;
|
|
174
|
+
slides?: number | undefined;
|
|
175
|
+
duration_seconds?: number | undefined;
|
|
176
|
+
warnings?: string[] | undefined;
|
|
177
|
+
storyboard_path: string;
|
|
178
|
+
error?: import("../server/errors.js").JobError | undefined;
|
|
179
|
+
job_id: string;
|
|
180
|
+
state: import("../server/queue.js").JobState;
|
|
181
|
+
stage: import("../server/queue.js").Stage | undefined;
|
|
182
|
+
queue_position: number | undefined;
|
|
183
|
+
elapsed_seconds: number;
|
|
184
|
+
steps: {
|
|
185
|
+
name: import("../server/queue.js").Stage;
|
|
186
|
+
state: import("../server/queue.js").StepState;
|
|
187
|
+
}[];
|
|
188
|
+
log: string[];
|
|
189
|
+
}>;
|
|
190
|
+
/** Poll, blocking up to `wait_seconds` for the job to move on. */
|
|
191
|
+
status(input: z.infer<typeof statusSchema>): Promise<{
|
|
192
|
+
next: string;
|
|
193
|
+
deck_path?: string | undefined;
|
|
194
|
+
slides?: number | undefined;
|
|
195
|
+
duration_seconds?: number | undefined;
|
|
196
|
+
warnings?: string[] | undefined;
|
|
197
|
+
storyboard_path: string;
|
|
198
|
+
error?: import("../server/errors.js").JobError | undefined;
|
|
199
|
+
job_id: string;
|
|
200
|
+
state: import("../server/queue.js").JobState;
|
|
201
|
+
stage: import("../server/queue.js").Stage | undefined;
|
|
202
|
+
queue_position: number | undefined;
|
|
203
|
+
elapsed_seconds: number;
|
|
204
|
+
steps: {
|
|
205
|
+
name: import("../server/queue.js").Stage;
|
|
206
|
+
state: import("../server/queue.js").StepState;
|
|
207
|
+
}[];
|
|
208
|
+
log: string[];
|
|
209
|
+
}>;
|
|
210
|
+
};
|
|
211
|
+
/** Where jobs write when nobody said. */
|
|
212
|
+
export declare function defaultWork(): string;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storyboard plus preferences, spoken.
|
|
3
|
+
*
|
|
4
|
+
* The unit of narration is the STOP, not the beat. A beat's stops are where a
|
|
5
|
+
* presenter pauses — its landing, then each reveal the emitter recorded a hold
|
|
6
|
+
* for — and giving each one its own audio file is what lets the deck advance on
|
|
7
|
+
* speech rather than on a number somebody guessed into `beat.seconds`. Arrive at
|
|
8
|
+
* a stop, play its segment, step when it ends.
|
|
9
|
+
*
|
|
10
|
+
* Which means the stop count is not ours to decide: it belongs to the emitter
|
|
11
|
+
* that draws the scene. So this asks — it calls `emitScene` and counts the holds
|
|
12
|
+
* exactly the way `buildStops` will at playback — rather than assuming a beat has
|
|
13
|
+
* as many stops as it has sentences, which would silently desynchronise every
|
|
14
|
+
* archetype whose reveal count depends on its params.
|
|
15
|
+
*/
|
|
16
|
+
import type { z } from "zod";
|
|
17
|
+
import type { Source, Storyboard } from "../types.js";
|
|
18
|
+
import { type Beat, type Format, type prefsSchema, type segmentSchema } from "../types.js";
|
|
19
|
+
import { type Runner } from "./tts.js";
|
|
20
|
+
type Prefs = z.infer<typeof prefsSchema>;
|
|
21
|
+
type Segment = z.infer<typeof segmentSchema>;
|
|
22
|
+
type Narration = {
|
|
23
|
+
voice: string;
|
|
24
|
+
beats: Record<string, Segment[]>;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* How many stops a beat has.
|
|
28
|
+
*
|
|
29
|
+
* Mirrors `buildStops`: the holds are deduplicated, a hold at the scene's own
|
|
30
|
+
* start is dropped (seeking there shows the frame before the entrance, i.e.
|
|
31
|
+
* nothing), the first survivor is the landing and the rest are steps. A scene
|
|
32
|
+
* with no holds at all is one stop — the slide itself.
|
|
33
|
+
*/
|
|
34
|
+
export declare function stopCount(holds: readonly number[]): number;
|
|
35
|
+
/**
|
|
36
|
+
* Ask the emitter how the beat is staged. Cheap: emitters build strings.
|
|
37
|
+
*
|
|
38
|
+
* A beat the emitter REFUSES answers one stop rather than throwing. Narration
|
|
39
|
+
* runs before the build and is not the stage that gets to decide a deck is dead:
|
|
40
|
+
* `planCut` emits every beat again and drops the refused ones through
|
|
41
|
+
* `onBeatError`, which is where a missing slide is reported and where a caller
|
|
42
|
+
* without that hook still gets the error. Throwing here instead killed the whole
|
|
43
|
+
* job one stage early, with the hook the caller had passed never reached.
|
|
44
|
+
*/
|
|
45
|
+
export declare function stopsFor(beat: Beat, source: Source, format: Format, sid?: string): number;
|
|
46
|
+
/**
|
|
47
|
+
* Split narration into sentences.
|
|
48
|
+
*
|
|
49
|
+
* The Latin terminators need whitespace or the end after them, so "0.5" and a
|
|
50
|
+
* trailing "et al." mid-clause stay whole; the CJK ones do not, because Chinese
|
|
51
|
+
* and Japanese put no space after 。 and a rule that waited for one would never
|
|
52
|
+
* split at all. Abbreviations like "e.g. " will still split — the cost is one
|
|
53
|
+
* clause landing on the wrong stop, which is a beat of drift, not a broken deck.
|
|
54
|
+
*/
|
|
55
|
+
export declare function splitSentences(text: string): string[];
|
|
56
|
+
/**
|
|
57
|
+
* Lay sentences over stops, one each, in order.
|
|
58
|
+
*
|
|
59
|
+
* Two mismatches, two rules. Fewer sentences than stops: hand out what there is
|
|
60
|
+
* and leave the later stops SILENT — a deck that invents a sentence to fill a
|
|
61
|
+
* reveal is a deck that says something the author did not. More sentences than
|
|
62
|
+
* stops: the surplus joins the last one, so the tail is spoken over the beat's
|
|
63
|
+
* final state instead of being dropped.
|
|
64
|
+
*
|
|
65
|
+
* Returns one entry per stop; `""` means nothing is said there.
|
|
66
|
+
*/
|
|
67
|
+
export declare function planSegments(text: string, stops: number): string[];
|
|
68
|
+
export interface NarrateOpts {
|
|
69
|
+
/** Directory the audio is written into. Returned paths are relative to it. */
|
|
70
|
+
dir: string;
|
|
71
|
+
/** Injected in tests so no test reaches the network. */
|
|
72
|
+
runner?: Runner;
|
|
73
|
+
/** Staging differs by canvas, so the stop count does too. */
|
|
74
|
+
format?: Format;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Narrate every beat that has something to say.
|
|
78
|
+
*
|
|
79
|
+
* Beats below a format's `minWeight` are narrated too: narration is stored in
|
|
80
|
+
* the pack, one storyboard renders as several formats, and re-synthesising the
|
|
81
|
+
* same sentence because a short dropped it is the one cost the cache exists to
|
|
82
|
+
* avoid. A beat with no `narration` text produces no segments at all rather than
|
|
83
|
+
* an empty array, so `Object.keys(narration.beats)` is the list of beats that
|
|
84
|
+
* actually speak.
|
|
85
|
+
*/
|
|
86
|
+
export declare function narrate(storyboard: Storyboard, source: Source, prefs: Prefs, opts: NarrateOpts): Promise<Narration>;
|
|
87
|
+
export {};
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import type { z } from "zod";
|
|
2
|
+
import type { cueSchema } from "../types.js";
|
|
3
|
+
type Cue = z.infer<typeof cueSchema>;
|
|
4
|
+
/** One synthesis job, with the two files edge-tts is asked to write. */
|
|
5
|
+
export interface SpeakJob {
|
|
6
|
+
text: string;
|
|
7
|
+
voice: string;
|
|
8
|
+
/** edge-tts prosody, e.g. "+10%" / "-5%". */
|
|
9
|
+
rate: string;
|
|
10
|
+
pitch: string;
|
|
11
|
+
/** Absolute path for the mp3. */
|
|
12
|
+
audio: string;
|
|
13
|
+
/** Absolute path for the SRT-shaped subtitle file. */
|
|
14
|
+
subs: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The two external programs, behind one seam.
|
|
18
|
+
*
|
|
19
|
+
* Injected rather than imported so no test needs the network — and so a caller
|
|
20
|
+
* with a different synthesiser can supply one without this module knowing.
|
|
21
|
+
*
|
|
22
|
+
* This is the LOW-level seam and it is edge-tts-shaped on purpose: two files on
|
|
23
|
+
* disk, one of them SRT. `SpeechProvider` below is the one to implement against
|
|
24
|
+
* for a different service.
|
|
25
|
+
*/
|
|
26
|
+
export interface Runner {
|
|
27
|
+
speak(job: SpeakJob): Promise<void>;
|
|
28
|
+
/** Seconds of rendered audio, or 0 when it cannot be measured. */
|
|
29
|
+
measure(audio: string): Promise<number>;
|
|
30
|
+
}
|
|
31
|
+
/** What a synthesiser is asked for. Paths are absolute; the mp3 must be written. */
|
|
32
|
+
export interface SpeechRequest {
|
|
33
|
+
text: string;
|
|
34
|
+
voice: string;
|
|
35
|
+
/** Prosody, in the SSML-ish notation edge-tts uses: "+10%", "-5Hz". */
|
|
36
|
+
rate: string;
|
|
37
|
+
pitch: string;
|
|
38
|
+
/** Where to write the audio. */
|
|
39
|
+
audio: string;
|
|
40
|
+
}
|
|
41
|
+
/** What comes back. `cues` may be empty; the deck then shows no subtitles. */
|
|
42
|
+
export interface SpeechResult {
|
|
43
|
+
seconds: number;
|
|
44
|
+
cues: Cue[];
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* A speech service, whole.
|
|
48
|
+
*
|
|
49
|
+
* The seam that matters for replacing edge-tts, and deliberately higher than
|
|
50
|
+
* `Runner`: a provider is handed text and hands back audio plus timing, and how
|
|
51
|
+
* it got the timing is its own business. `Runner` makes the caller write an SRT
|
|
52
|
+
* file and parse it, which is edge-tts's implementation showing through — a
|
|
53
|
+
* hosted API returning JSON word timings would have to fabricate a subtitle file
|
|
54
|
+
* to satisfy it.
|
|
55
|
+
*
|
|
56
|
+
* `check` is separate from `speak` so a missing binary or a bad key is reported
|
|
57
|
+
* before a job is accepted rather than a minute into one.
|
|
58
|
+
*/
|
|
59
|
+
export interface SpeechProvider {
|
|
60
|
+
readonly id: string;
|
|
61
|
+
check(): Promise<void>;
|
|
62
|
+
speak(req: SpeechRequest): Promise<SpeechResult>;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Find edge-tts, or say how to install it.
|
|
66
|
+
*
|
|
67
|
+
* A missing binary otherwise surfaces as ENOENT from `spawn` halfway through a
|
|
68
|
+
* build, which tells the user nothing they can act on. Memoised: the probe is a
|
|
69
|
+
* process launch, and a deck asks for dozens of segments.
|
|
70
|
+
*/
|
|
71
|
+
export declare function resolveEdgeTts(can?: (argv: string[]) => Promise<boolean>): Promise<string[]>;
|
|
72
|
+
/** edge-tts over the network, ffprobe over the file. The production `Runner`. */
|
|
73
|
+
export declare const edgeTts: Runner;
|
|
74
|
+
/**
|
|
75
|
+
* Parse edge-tts' subtitle file into cues.
|
|
76
|
+
*
|
|
77
|
+
* Deliberately forgiving about the block numbers and about blank-line spacing:
|
|
78
|
+
* the file is machine-written, but it is also the one thing here we did not
|
|
79
|
+
* write, and a cue lost to a strict parser is a subtitle that silently vanishes.
|
|
80
|
+
*/
|
|
81
|
+
export declare function parseCues(srt: string): Cue[];
|
|
82
|
+
export interface SynthOpts {
|
|
83
|
+
voice: string;
|
|
84
|
+
rate?: string;
|
|
85
|
+
pitch?: string;
|
|
86
|
+
/** Directory the mp3 and its sidecar are written into. Created if absent. */
|
|
87
|
+
dir: string;
|
|
88
|
+
/** The low-level edge-tts seam. Ignored when `provider` is given. */
|
|
89
|
+
runner?: Runner;
|
|
90
|
+
/** A whole synthesiser. The seam to use for anything that is not edge-tts. */
|
|
91
|
+
provider?: SpeechProvider;
|
|
92
|
+
}
|
|
93
|
+
export interface Speech {
|
|
94
|
+
/** Absolute path to the mp3. */
|
|
95
|
+
audio: string;
|
|
96
|
+
/** Its filename, which is also the cache key — what a caller stores. */
|
|
97
|
+
file: string;
|
|
98
|
+
seconds: number;
|
|
99
|
+
cues: Cue[];
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* edge-tts as a provider: it writes the mp3 and an SRT beside it, this parses
|
|
103
|
+
* the SRT and measures the mp3, and the SRT is gone before anyone sees it.
|
|
104
|
+
*
|
|
105
|
+
* Everything edge-tts-specific ends here. A second provider implements `speak`
|
|
106
|
+
* and returns its own timings; nothing upstream changes.
|
|
107
|
+
*/
|
|
108
|
+
export declare function edgeProvider(runner?: Runner): SpeechProvider;
|
|
109
|
+
/**
|
|
110
|
+
* Providers by name, and how one is chosen.
|
|
111
|
+
*
|
|
112
|
+
* A record rather than a switch, so adding a service is adding an entry. The
|
|
113
|
+
* environment variable exists so a deployment can change synthesiser without a
|
|
114
|
+
* rebuild — which is the whole reason this seam is here.
|
|
115
|
+
*/
|
|
116
|
+
export declare const PROVIDERS: Record<string, () => SpeechProvider>;
|
|
117
|
+
export declare function resolveProvider(name?: string): SpeechProvider;
|
|
118
|
+
/**
|
|
119
|
+
* The cache key, and therefore the filename.
|
|
120
|
+
*
|
|
121
|
+
* Content-addressed on everything that changes the sound, so re-narrating an
|
|
122
|
+
* edited deck re-synthesises only the beats whose words moved. That is the
|
|
123
|
+
* difference between a four-second edit loop and a three-minute one, and it is
|
|
124
|
+
* also why two beats that say the same sentence share one file.
|
|
125
|
+
*/
|
|
126
|
+
export declare function cacheKey(text: string, voice: string, rate: string, pitch: string): string;
|
|
127
|
+
/**
|
|
128
|
+
* Speak `text`, and return where it landed and how long it takes.
|
|
129
|
+
*
|
|
130
|
+
* `seconds` is measured, never estimated — it is what lets a stop last exactly
|
|
131
|
+
* as long as the sentence spoken at it.
|
|
132
|
+
*/
|
|
133
|
+
export declare function synthesize(text: string, opts: SynthOpts): Promise<Speech>;
|
|
134
|
+
export {};
|