@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.
Files changed (78) hide show
  1. package/README.md +794 -0
  2. package/dist/cli.js +8906 -0
  3. package/dist/deck-runtime.js +55 -0
  4. package/dist/index.js +8590 -0
  5. package/dist/mcp.js +7809 -0
  6. package/dist/server/errors.js +73 -0
  7. package/dist/server/http.js +504 -0
  8. package/dist/server/main.js +91 -0
  9. package/dist/server/options.js +198 -0
  10. package/dist/server/pipeline.js +356 -0
  11. package/dist/server/queue.js +195 -0
  12. package/dist/server/ui.js +1614 -0
  13. package/dist/server/upload.js +232 -0
  14. package/dist/types/cli.d.ts +1 -0
  15. package/dist/types/deck/runtime.d.ts +59 -0
  16. package/dist/types/deck/subtitles.d.ts +101 -0
  17. package/dist/types/emit/archetypes/annotated-figure.d.ts +103 -0
  18. package/dist/types/emit/archetypes/bar-compare.d.ts +30 -0
  19. package/dist/types/emit/archetypes/callout.d.ts +7 -0
  20. package/dist/types/emit/archetypes/claim-figure.d.ts +9 -0
  21. package/dist/types/emit/archetypes/data-table.d.ts +21 -0
  22. package/dist/types/emit/archetypes/equation-walk.d.ts +2 -0
  23. package/dist/types/emit/archetypes/grid.d.ts +16 -0
  24. package/dist/types/emit/archetypes/index.d.ts +19 -0
  25. package/dist/types/emit/archetypes/line-chart.d.ts +22 -0
  26. package/dist/types/emit/archetypes/pipeline.d.ts +81 -0
  27. package/dist/types/emit/archetypes/split-compare.d.ts +2 -0
  28. package/dist/types/emit/archetypes/stack.d.ts +93 -0
  29. package/dist/types/emit/archetypes/title.d.ts +188 -0
  30. package/dist/types/emit/camera.d.ts +397 -0
  31. package/dist/types/emit/composition.d.ts +191 -0
  32. package/dist/types/emit/island.d.ts +19 -0
  33. package/dist/types/emit/kit.d.ts +256 -0
  34. package/dist/types/emit/svg.d.ts +177 -0
  35. package/dist/types/emit/theme.d.ts +65 -0
  36. package/dist/types/emit/themes/index.d.ts +40 -0
  37. package/dist/types/emit/themes/ink.d.ts +12 -0
  38. package/dist/types/emit/themes/mono.d.ts +20 -0
  39. package/dist/types/emit/themes/paper.d.ts +18 -0
  40. package/dist/types/index.d.ts +200 -0
  41. package/dist/types/mcp/main.d.ts +2 -0
  42. package/dist/types/mcp/prereqs.d.ts +22 -0
  43. package/dist/types/mcp/tools.d.ts +212 -0
  44. package/dist/types/narrate/narrate.d.ts +87 -0
  45. package/dist/types/narrate/tts.d.ts +134 -0
  46. package/dist/types/narrate/voices.d.ts +29 -0
  47. package/dist/types/pack/media.d.ts +61 -0
  48. package/dist/types/pack/pack.d.ts +16 -0
  49. package/dist/types/plan/codex.d.ts +24 -0
  50. package/dist/types/plan/duration.d.ts +394 -0
  51. package/dist/types/plan/prompt.d.ts +47 -0
  52. package/dist/types/plan/refs.d.ts +22 -0
  53. package/dist/types/plan/select.d.ts +116 -0
  54. package/dist/types/prefs.d.ts +43 -0
  55. package/dist/types/render/captions.d.ts +108 -0
  56. package/dist/types/render/ffmpeg.d.ts +129 -0
  57. package/dist/types/render/render.d.ts +123 -0
  58. package/dist/types/render/timing.d.ts +290 -0
  59. package/dist/types/server/errors.d.ts +11 -0
  60. package/dist/types/server/http.d.ts +57 -0
  61. package/dist/types/server/main.d.ts +1 -0
  62. package/dist/types/server/options.d.ts +90 -0
  63. package/dist/types/server/pipeline.d.ts +21 -0
  64. package/dist/types/server/queue.d.ts +105 -0
  65. package/dist/types/server/ui.d.ts +9 -0
  66. package/dist/types/server/upload.d.ts +107 -0
  67. package/dist/types/source/assets.d.ts +11 -0
  68. package/dist/types/source/fonts.d.ts +15 -0
  69. package/dist/types/source/markdown.d.ts +8 -0
  70. package/dist/types/types.d.ts +1992 -0
  71. package/dist/types/verify/budget.d.ts +41 -0
  72. package/dist/types/verify/check.d.ts +78 -0
  73. package/dist/types/verify/drift.d.ts +158 -0
  74. package/dist/types/verify/fidelity.d.ts +247 -0
  75. package/dist/types/verify/index.d.ts +207 -0
  76. package/dist/types/verify/overprint.d.ts +133 -0
  77. package/dist/types/verify/typefloor.d.ts +50 -0
  78. 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,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -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 {};