@scenar/cli 0.10.0 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/example-bundle/assets/index-DxmqjG-8.js +388 -0
- package/example-bundle/assets/style-B2sxnVVZ.css +1 -0
- package/example-bundle/embed.js +1 -1
- package/example-bundle/index.html +2 -2
- package/example-bundle/logo.png +0 -0
- package/example-bundle/pack-manifest.json +33 -9
- package/example-bundle/soundtrack/music.mp3 +0 -0
- package/example-bundle/soundtrack/sfx/click.mp3 +0 -0
- package/example-bundle/soundtrack/sfx/keystroke.mp3 +0 -0
- package/package.json +4 -4
- package/src/__tests__/collect-pack-shots.test.ts +8 -0
- package/src/__tests__/elevenlabs-provider.test.ts +187 -0
- package/src/__tests__/embed-snippet.test.ts +5 -0
- package/src/__tests__/find-authored-soundtrack.test.ts +54 -0
- package/src/__tests__/find-authored-title-cards.test.ts +65 -0
- package/src/__tests__/load-bundle.test.ts +67 -0
- package/src/__tests__/narrate-command.test.ts +65 -1
- package/src/__tests__/narration-cache.test.ts +134 -0
- package/src/__tests__/pack-bundle-contract.test.ts +2 -0
- package/src/__tests__/pack-generate-embed-entry.test.ts +95 -16
- package/src/__tests__/presenter-command.test.ts +366 -0
- package/src/__tests__/render-command.test.ts +11 -0
- package/src/__tests__/render-generate-entry.test.ts +127 -0
- package/src/__tests__/resolve-provider.test.ts +32 -3
- package/src/__tests__/scenario-validator.test.ts +174 -0
- package/src/__tests__/stage-assets.test.ts +269 -0
- package/src/commands/narrate.js +1 -1
- package/src/commands/narrate.js.map +1 -1
- package/src/commands/narrate.ts +1 -1
- package/src/commands/presenter.d.ts +3 -0
- package/src/commands/presenter.d.ts.map +1 -0
- package/src/commands/presenter.js +89 -0
- package/src/commands/presenter.js.map +1 -0
- package/src/commands/presenter.ts +107 -0
- package/src/commands/render.d.ts.map +1 -1
- package/src/commands/render.js +2 -0
- package/src/commands/render.js.map +1 -1
- package/src/commands/render.ts +6 -0
- package/src/embed/embed-snippet.d.ts +5 -0
- package/src/embed/embed-snippet.d.ts.map +1 -1
- package/src/embed/embed-snippet.js +6 -1
- package/src/embed/embed-snippet.js.map +1 -1
- package/src/embed/embed-snippet.ts +6 -1
- package/src/index.d.ts.map +1 -1
- package/src/index.js +2 -0
- package/src/index.js.map +1 -1
- package/src/index.ts +2 -0
- package/src/narrate/run-narrate.d.ts +15 -1
- package/src/narrate/run-narrate.d.ts.map +1 -1
- package/src/narrate/run-narrate.js +11 -4
- package/src/narrate/run-narrate.js.map +1 -1
- package/src/narrate/run-narrate.ts +15 -6
- package/src/pack/bundle-contract.d.ts +1 -0
- package/src/pack/bundle-contract.d.ts.map +1 -1
- package/src/pack/bundle-contract.js +3 -0
- package/src/pack/bundle-contract.js.map +1 -1
- package/src/pack/bundle-contract.ts +3 -0
- package/src/pack/collect-pack-shots.d.ts +5 -1
- package/src/pack/collect-pack-shots.d.ts.map +1 -1
- package/src/pack/collect-pack-shots.js +3 -1
- package/src/pack/collect-pack-shots.js.map +1 -1
- package/src/pack/collect-pack-shots.ts +15 -1
- package/src/pack/generate-embed-entry.d.ts +7 -0
- package/src/pack/generate-embed-entry.d.ts.map +1 -1
- package/src/pack/generate-embed-entry.js +102 -7
- package/src/pack/generate-embed-entry.js.map +1 -1
- package/src/pack/generate-embed-entry.ts +107 -7
- package/src/pack/run-pack.d.ts +1 -0
- package/src/pack/run-pack.d.ts.map +1 -1
- package/src/pack/run-pack.js +100 -9
- package/src/pack/run-pack.js.map +1 -1
- package/src/pack/run-pack.ts +133 -9
- package/src/presenter/heygen-client.d.ts +69 -0
- package/src/presenter/heygen-client.d.ts.map +1 -0
- package/src/presenter/heygen-client.js +154 -0
- package/src/presenter/heygen-client.js.map +1 -0
- package/src/presenter/heygen-client.ts +224 -0
- package/src/presenter/run-presenter.d.ts +67 -0
- package/src/presenter/run-presenter.d.ts.map +1 -0
- package/src/presenter/run-presenter.js +348 -0
- package/src/presenter/run-presenter.js.map +1 -0
- package/src/presenter/run-presenter.ts +536 -0
- package/src/render/generate-entry.d.ts +4 -0
- package/src/render/generate-entry.d.ts.map +1 -1
- package/src/render/generate-entry.js +87 -9
- package/src/render/generate-entry.js.map +1 -1
- package/src/render/generate-entry.ts +96 -9
- package/src/render/run-render.d.ts +6 -0
- package/src/render/run-render.d.ts.map +1 -1
- package/src/render/run-render.js +58 -0
- package/src/render/run-render.js.map +1 -1
- package/src/render/run-render.ts +72 -0
- package/src/render/stage-assets.d.ts +44 -0
- package/src/render/stage-assets.d.ts.map +1 -0
- package/src/render/stage-assets.js +91 -0
- package/src/render/stage-assets.js.map +1 -0
- package/src/render/stage-assets.ts +130 -0
- package/src/tts/echogarden.d.ts.map +1 -1
- package/src/tts/echogarden.js +4 -2
- package/src/tts/echogarden.js.map +1 -1
- package/src/tts/echogarden.ts +5 -2
- package/src/tts/edge-tts.d.ts.map +1 -1
- package/src/tts/edge-tts.js +2 -1
- package/src/tts/edge-tts.js.map +1 -1
- package/src/tts/edge-tts.ts +2 -1
- package/src/tts/elevenlabs.d.ts +15 -0
- package/src/tts/elevenlabs.d.ts.map +1 -0
- package/src/tts/elevenlabs.js +73 -0
- package/src/tts/elevenlabs.js.map +1 -0
- package/src/tts/elevenlabs.ts +105 -0
- package/src/tts/openai.d.ts.map +1 -1
- package/src/tts/openai.js +2 -1
- package/src/tts/openai.js.map +1 -1
- package/src/tts/openai.ts +2 -1
- package/src/tts/resolve-provider.d.ts +1 -1
- package/src/tts/resolve-provider.d.ts.map +1 -1
- package/src/tts/resolve-provider.js +10 -3
- package/src/tts/resolve-provider.js.map +1 -1
- package/src/tts/resolve-provider.ts +12 -4
- package/src/tts/types.d.ts +9 -0
- package/src/tts/types.d.ts.map +1 -1
- package/src/tts/types.ts +10 -0
- package/src/util/confirm.d.ts +11 -0
- package/src/util/confirm.d.ts.map +1 -0
- package/src/util/confirm.js +24 -0
- package/src/util/confirm.js.map +1 -0
- package/src/util/confirm.ts +23 -0
- package/src/util/load-bundle.d.ts +17 -1
- package/src/util/load-bundle.d.ts.map +1 -1
- package/src/util/load-bundle.js +19 -11
- package/src/util/load-bundle.js.map +1 -1
- package/src/util/load-bundle.ts +38 -12
- package/src/util/load-ts.d.ts +77 -0
- package/src/util/load-ts.d.ts.map +1 -1
- package/src/util/load-ts.js +125 -0
- package/src/util/load-ts.js.map +1 -1
- package/src/util/load-ts.ts +165 -0
- package/src/util/narration-cache.d.ts +13 -5
- package/src/util/narration-cache.d.ts.map +1 -1
- package/src/util/narration-cache.js +10 -8
- package/src/util/narration-cache.js.map +1 -1
- package/src/util/narration-cache.ts +22 -6
- package/src/util/presenter-cache.d.ts +44 -0
- package/src/util/presenter-cache.d.ts.map +1 -0
- package/src/util/presenter-cache.js +43 -0
- package/src/util/presenter-cache.js.map +1 -0
- package/src/util/presenter-cache.ts +101 -0
- package/src/util/scenario-assets.d.ts +47 -0
- package/src/util/scenario-assets.d.ts.map +1 -0
- package/src/util/scenario-assets.js +71 -0
- package/src/util/scenario-assets.js.map +1 -0
- package/src/util/scenario-assets.ts +111 -0
- package/src/util/soundtrack-assets.d.ts +34 -0
- package/src/util/soundtrack-assets.d.ts.map +1 -0
- package/src/util/soundtrack-assets.js +48 -0
- package/src/util/soundtrack-assets.js.map +1 -0
- package/src/util/soundtrack-assets.ts +69 -0
- package/src/validate/scenario-validator.d.ts.map +1 -1
- package/src/validate/scenario-validator.js +82 -0
- package/src/validate/scenario-validator.js.map +1 -1
- package/src/validate/scenario-validator.ts +110 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/example-bundle/assets/index-DpJUOVBT.js +0 -388
- package/example-bundle/assets/style-ix_q0zsc.css +0 -1
package/src/pack/run-pack.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { resolve, join, basename } from "node:path";
|
|
1
|
+
import { resolve, join, basename, dirname } from "node:path";
|
|
2
2
|
import { stat, mkdir, writeFile, rm, readdir, copyFile, access } from "node:fs/promises";
|
|
3
3
|
import { detectRenderExport } from "../render/detect-render-export.js";
|
|
4
4
|
import { resolveProvidersPath } from "../render/resolve-providers.js";
|
|
5
|
+
import { SFX_DEST_PATHS, resolveMusicAsset, resolveSfxAssetPaths, } from "../util/soundtrack-assets.js";
|
|
6
|
+
import { resolveLogoAsset } from "../util/scenario-assets.js";
|
|
5
7
|
import { generateEmbedEntry, generateEmbedHtml } from "./generate-embed-entry.js";
|
|
6
8
|
import { collectPackShots } from "./collect-pack-shots.js";
|
|
7
9
|
import { runViteBuild } from "./build.js";
|
|
@@ -39,10 +41,12 @@ export async function runPack(options) {
|
|
|
39
41
|
const renderFilePath = await detectRenderExport(scenarioDir);
|
|
40
42
|
const providersPath = await resolveProvidersPath(scenarioDir);
|
|
41
43
|
const hasNarration = await fileExists(join(scenarioDir, "narration", "manifest.json"));
|
|
44
|
+
const hasPresenter = await fileExists(join(scenarioDir, "presenter", "manifest.json"));
|
|
42
45
|
onLog(`Scenario: ${scenarioId}`);
|
|
43
46
|
onLog(`Render: ${renderFilePath}`);
|
|
44
47
|
onLog(`Providers: ${providersPath ?? "none"}`);
|
|
45
48
|
onLog(`Narration: ${hasNarration ? "yes (manifest found)" : "none"}`);
|
|
49
|
+
onLog(`Presenter: ${hasPresenter ? "yes (manifest found)" : "none"}`);
|
|
46
50
|
onLog(`Output: ${outDir}`);
|
|
47
51
|
// 1. Discover the declared shots — and the authored viewport — by
|
|
48
52
|
// SSR-loading the steps module (same Vite resolution as the build
|
|
@@ -72,6 +76,7 @@ export async function runPack(options) {
|
|
|
72
76
|
renderFilePath,
|
|
73
77
|
scenarioId,
|
|
74
78
|
hasNarration,
|
|
79
|
+
hasPresenter,
|
|
75
80
|
providersPath,
|
|
76
81
|
canonicalWidth: width,
|
|
77
82
|
shellHeight,
|
|
@@ -89,7 +94,39 @@ export async function runPack(options) {
|
|
|
89
94
|
// ./narration/manifest.json at runtime and resolves each clip's
|
|
90
95
|
// relative src against it, so both must ship in the bundle.
|
|
91
96
|
if (hasNarration) {
|
|
92
|
-
await
|
|
97
|
+
await copyClipTrack(scenarioDir, outDir, "narration", ".mp3");
|
|
98
|
+
}
|
|
99
|
+
// 4a. Copy the presenter manifest + clips — the same runtime contract
|
|
100
|
+
// as narration: the embed fetches ./presenter/manifest.json and
|
|
101
|
+
// resolves each clip's relative src against it. CSP is
|
|
102
|
+
// `media-src 'self'`, so the clips must ship in the bundle.
|
|
103
|
+
if (hasPresenter) {
|
|
104
|
+
await copyClipTrack(scenarioDir, outDir, "presenter", ".mp4");
|
|
105
|
+
}
|
|
106
|
+
// 4b. Copy the soundtrack's assets: the authored music file (at its
|
|
107
|
+
// scenario-relative path, which is what the embed references) and
|
|
108
|
+
// the built-in SFX set from @scenar/react. Discovery rides the same
|
|
109
|
+
// SSR load as the shots; when the module was not loadable, the
|
|
110
|
+
// soundtrack (if any) cannot ship — warn rather than silently
|
|
111
|
+
// packing an embed whose audio 404s.
|
|
112
|
+
if (collectedShots.recorded) {
|
|
113
|
+
if (collectedShots.authoredSoundtrack) {
|
|
114
|
+
const copied = await copySoundtrack(scenarioDir, outDir, collectedShots.authoredSoundtrack);
|
|
115
|
+
if (copied > 0)
|
|
116
|
+
onLog(`Soundtrack: ${copied} audio file(s) copied into the bundle`);
|
|
117
|
+
}
|
|
118
|
+
// 4c. Copy title-card logos at their scenario-relative paths (the
|
|
119
|
+
// card component references them by that same path). Discovery
|
|
120
|
+
// rides the same SSR load as the shots and the soundtrack.
|
|
121
|
+
if (collectedShots.authoredTitleCards) {
|
|
122
|
+
const copied = await copyTitleCardLogos(scenarioDir, outDir, collectedShots.authoredTitleCards);
|
|
123
|
+
if (copied > 0)
|
|
124
|
+
onLog(`Cards: ${copied} logo file(s) copied into the bundle`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
onLog("Warning: steps module not loadable under Node — if this scenario authors a " +
|
|
129
|
+
"soundtrack or title cards, their asset files were NOT copied into the bundle.");
|
|
93
130
|
}
|
|
94
131
|
// 5. Write the required scenario.json descriptor, recording the canonical
|
|
95
132
|
// viewport baked into the bundle so `deploy`/`serve`/`publish` can derive
|
|
@@ -114,6 +151,7 @@ export async function runPack(options) {
|
|
|
114
151
|
renderFilePath,
|
|
115
152
|
providersPath,
|
|
116
153
|
hasNarration,
|
|
154
|
+
hasPresenter,
|
|
117
155
|
shots: collectedShots.recorded ? collectedShots.shots : undefined,
|
|
118
156
|
manifest,
|
|
119
157
|
totalBytes,
|
|
@@ -126,24 +164,77 @@ export async function runPack(options) {
|
|
|
126
164
|
}
|
|
127
165
|
}
|
|
128
166
|
/**
|
|
129
|
-
* Copy
|
|
130
|
-
*
|
|
131
|
-
*
|
|
167
|
+
* Copy one clip track's manifest and media (narration mp3s, presenter
|
|
168
|
+
* mp4s) from <scenarioDir>/<trackDir> into <outDir>/<trackDir>. The
|
|
169
|
+
* manifest is the runtime index the embed fetches; the clips are what it
|
|
170
|
+
* references by relative src. Cache files never ship.
|
|
132
171
|
*/
|
|
133
|
-
async function
|
|
134
|
-
const srcDir = join(scenarioDir,
|
|
135
|
-
const destDir = join(outDir,
|
|
172
|
+
async function copyClipTrack(scenarioDir, outDir, trackDir, extension) {
|
|
173
|
+
const srcDir = join(scenarioDir, trackDir);
|
|
174
|
+
const destDir = join(outDir, trackDir);
|
|
136
175
|
await mkdir(destDir, { recursive: true });
|
|
137
176
|
const entries = await readdir(srcDir, { withFileTypes: true });
|
|
138
177
|
for (const entry of entries) {
|
|
139
178
|
if (!entry.isFile())
|
|
140
179
|
continue;
|
|
141
180
|
const name = entry.name.toLowerCase();
|
|
142
|
-
if (name === "manifest.json" || name.endsWith(
|
|
181
|
+
if (name === "manifest.json" || name.endsWith(extension)) {
|
|
143
182
|
await copyFile(join(srcDir, entry.name), join(destDir, entry.name));
|
|
144
183
|
}
|
|
145
184
|
}
|
|
146
185
|
}
|
|
186
|
+
/**
|
|
187
|
+
* Copy the soundtrack's audio into the bundle: the authored music file at
|
|
188
|
+
* its scenario-relative path (the embed references it by that same path),
|
|
189
|
+
* and the built-in SFX set from @scenar/react at the shared
|
|
190
|
+
* {@link SFX_DEST_PATHS} locations the generated entry points at. Returns
|
|
191
|
+
* the number of files copied. Remote music URLs copy nothing.
|
|
192
|
+
*/
|
|
193
|
+
async function copySoundtrack(scenarioDir, outDir, soundtrack) {
|
|
194
|
+
let copied = 0;
|
|
195
|
+
if (soundtrack.musicSrc) {
|
|
196
|
+
const music = await resolveMusicAsset(scenarioDir, soundtrack.musicSrc);
|
|
197
|
+
if (music) {
|
|
198
|
+
const dest = join(outDir, music.destRelPath);
|
|
199
|
+
await mkdir(dirname(dest), { recursive: true });
|
|
200
|
+
await copyFile(music.sourcePath, dest);
|
|
201
|
+
copied += 1;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (soundtrack.sfx) {
|
|
205
|
+
const sfxPaths = resolveSfxAssetPaths(scenarioDir);
|
|
206
|
+
for (const sound of ["click", "keystroke"]) {
|
|
207
|
+
const dest = join(outDir, SFX_DEST_PATHS[sound]);
|
|
208
|
+
await mkdir(dirname(dest), { recursive: true });
|
|
209
|
+
await copyFile(sfxPaths[sound], dest);
|
|
210
|
+
copied += 1;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return copied;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Copy title-card logos into the bundle at their scenario-relative paths.
|
|
217
|
+
* A logo shared by intro and outro copies once; remote URLs copy nothing.
|
|
218
|
+
* Returns the number of files copied.
|
|
219
|
+
*/
|
|
220
|
+
async function copyTitleCardLogos(scenarioDir, outDir, titleCards) {
|
|
221
|
+
let copied = 0;
|
|
222
|
+
const copiedPaths = new Set();
|
|
223
|
+
for (const side of ["intro", "outro"]) {
|
|
224
|
+
const logoSrc = titleCards[side]?.logoSrc;
|
|
225
|
+
if (!logoSrc)
|
|
226
|
+
continue;
|
|
227
|
+
const logo = await resolveLogoAsset(scenarioDir, logoSrc, `titleCards.${side}.logoSrc`);
|
|
228
|
+
if (!logo || copiedPaths.has(logo.destRelPath))
|
|
229
|
+
continue;
|
|
230
|
+
const dest = join(outDir, logo.destRelPath);
|
|
231
|
+
await mkdir(dirname(dest), { recursive: true });
|
|
232
|
+
await copyFile(logo.sourcePath, dest);
|
|
233
|
+
copiedPaths.add(logo.destRelPath);
|
|
234
|
+
copied += 1;
|
|
235
|
+
}
|
|
236
|
+
return copied;
|
|
237
|
+
}
|
|
147
238
|
/** One log-friendly line for the shot discovery outcome. */
|
|
148
239
|
function describeCollectedShots(collected) {
|
|
149
240
|
if (!collected.recorded) {
|
package/src/pack/run-pack.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-pack.js","sourceRoot":"","sources":["../../../src/pack/run-pack.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"run-pack.js","sourceRoot":"","sources":["../../../src/pack/run-pack.ts"],"names":[],"mappings":"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"}
|
package/src/pack/run-pack.ts
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
|
-
import { resolve, join, basename } from "node:path";
|
|
1
|
+
import { resolve, join, basename, dirname } from "node:path";
|
|
2
2
|
import { stat, mkdir, writeFile, rm, readdir, copyFile, access } from "node:fs/promises";
|
|
3
3
|
import { detectRenderExport } from "../render/detect-render-export.js";
|
|
4
4
|
import { resolveProvidersPath } from "../render/resolve-providers.js";
|
|
5
|
+
import type { AuthoredSoundtrack, AuthoredTitleCards } from "../util/load-ts.js";
|
|
6
|
+
import {
|
|
7
|
+
SFX_DEST_PATHS,
|
|
8
|
+
resolveMusicAsset,
|
|
9
|
+
resolveSfxAssetPaths,
|
|
10
|
+
} from "../util/soundtrack-assets.js";
|
|
11
|
+
import { resolveLogoAsset } from "../util/scenario-assets.js";
|
|
5
12
|
import { generateEmbedEntry, generateEmbedHtml } from "./generate-embed-entry.js";
|
|
6
13
|
import { collectPackShots, type CollectedPackShots } from "./collect-pack-shots.js";
|
|
7
14
|
import { runViteBuild } from "./build.js";
|
|
@@ -55,6 +62,7 @@ export interface PackResult {
|
|
|
55
62
|
readonly renderFilePath: string;
|
|
56
63
|
readonly providersPath: string | null;
|
|
57
64
|
readonly hasNarration: boolean;
|
|
65
|
+
readonly hasPresenter: boolean;
|
|
58
66
|
/**
|
|
59
67
|
* The declared shot names recorded in scenario.json (empty = the scenario
|
|
60
68
|
* authoritatively declares none), or undefined when the steps module could
|
|
@@ -97,11 +105,13 @@ export async function runPack(options: RunPackOptions): Promise<PackResult> {
|
|
|
97
105
|
const renderFilePath = await detectRenderExport(scenarioDir);
|
|
98
106
|
const providersPath = await resolveProvidersPath(scenarioDir);
|
|
99
107
|
const hasNarration = await fileExists(join(scenarioDir, "narration", "manifest.json"));
|
|
108
|
+
const hasPresenter = await fileExists(join(scenarioDir, "presenter", "manifest.json"));
|
|
100
109
|
|
|
101
110
|
onLog(`Scenario: ${scenarioId}`);
|
|
102
111
|
onLog(`Render: ${renderFilePath}`);
|
|
103
112
|
onLog(`Providers: ${providersPath ?? "none"}`);
|
|
104
113
|
onLog(`Narration: ${hasNarration ? "yes (manifest found)" : "none"}`);
|
|
114
|
+
onLog(`Presenter: ${hasPresenter ? "yes (manifest found)" : "none"}`);
|
|
105
115
|
onLog(`Output: ${outDir}`);
|
|
106
116
|
|
|
107
117
|
// 1. Discover the declared shots — and the authored viewport — by
|
|
@@ -137,6 +147,7 @@ export async function runPack(options: RunPackOptions): Promise<PackResult> {
|
|
|
137
147
|
renderFilePath,
|
|
138
148
|
scenarioId,
|
|
139
149
|
hasNarration,
|
|
150
|
+
hasPresenter,
|
|
140
151
|
providersPath,
|
|
141
152
|
canonicalWidth: width,
|
|
142
153
|
shellHeight,
|
|
@@ -156,7 +167,48 @@ export async function runPack(options: RunPackOptions): Promise<PackResult> {
|
|
|
156
167
|
// ./narration/manifest.json at runtime and resolves each clip's
|
|
157
168
|
// relative src against it, so both must ship in the bundle.
|
|
158
169
|
if (hasNarration) {
|
|
159
|
-
await
|
|
170
|
+
await copyClipTrack(scenarioDir, outDir, "narration", ".mp3");
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// 4a. Copy the presenter manifest + clips — the same runtime contract
|
|
174
|
+
// as narration: the embed fetches ./presenter/manifest.json and
|
|
175
|
+
// resolves each clip's relative src against it. CSP is
|
|
176
|
+
// `media-src 'self'`, so the clips must ship in the bundle.
|
|
177
|
+
if (hasPresenter) {
|
|
178
|
+
await copyClipTrack(scenarioDir, outDir, "presenter", ".mp4");
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// 4b. Copy the soundtrack's assets: the authored music file (at its
|
|
182
|
+
// scenario-relative path, which is what the embed references) and
|
|
183
|
+
// the built-in SFX set from @scenar/react. Discovery rides the same
|
|
184
|
+
// SSR load as the shots; when the module was not loadable, the
|
|
185
|
+
// soundtrack (if any) cannot ship — warn rather than silently
|
|
186
|
+
// packing an embed whose audio 404s.
|
|
187
|
+
if (collectedShots.recorded) {
|
|
188
|
+
if (collectedShots.authoredSoundtrack) {
|
|
189
|
+
const copied = await copySoundtrack(
|
|
190
|
+
scenarioDir,
|
|
191
|
+
outDir,
|
|
192
|
+
collectedShots.authoredSoundtrack,
|
|
193
|
+
);
|
|
194
|
+
if (copied > 0) onLog(`Soundtrack: ${copied} audio file(s) copied into the bundle`);
|
|
195
|
+
}
|
|
196
|
+
// 4c. Copy title-card logos at their scenario-relative paths (the
|
|
197
|
+
// card component references them by that same path). Discovery
|
|
198
|
+
// rides the same SSR load as the shots and the soundtrack.
|
|
199
|
+
if (collectedShots.authoredTitleCards) {
|
|
200
|
+
const copied = await copyTitleCardLogos(
|
|
201
|
+
scenarioDir,
|
|
202
|
+
outDir,
|
|
203
|
+
collectedShots.authoredTitleCards,
|
|
204
|
+
);
|
|
205
|
+
if (copied > 0) onLog(`Cards: ${copied} logo file(s) copied into the bundle`);
|
|
206
|
+
}
|
|
207
|
+
} else {
|
|
208
|
+
onLog(
|
|
209
|
+
"Warning: steps module not loadable under Node — if this scenario authors a " +
|
|
210
|
+
"soundtrack or title cards, their asset files were NOT copied into the bundle.",
|
|
211
|
+
);
|
|
160
212
|
}
|
|
161
213
|
|
|
162
214
|
// 5. Write the required scenario.json descriptor, recording the canonical
|
|
@@ -191,6 +243,7 @@ export async function runPack(options: RunPackOptions): Promise<PackResult> {
|
|
|
191
243
|
renderFilePath,
|
|
192
244
|
providersPath,
|
|
193
245
|
hasNarration,
|
|
246
|
+
hasPresenter,
|
|
194
247
|
shots: collectedShots.recorded ? collectedShots.shots : undefined,
|
|
195
248
|
manifest,
|
|
196
249
|
totalBytes,
|
|
@@ -203,24 +256,95 @@ export async function runPack(options: RunPackOptions): Promise<PackResult> {
|
|
|
203
256
|
}
|
|
204
257
|
|
|
205
258
|
/**
|
|
206
|
-
* Copy
|
|
207
|
-
*
|
|
208
|
-
*
|
|
259
|
+
* Copy one clip track's manifest and media (narration mp3s, presenter
|
|
260
|
+
* mp4s) from <scenarioDir>/<trackDir> into <outDir>/<trackDir>. The
|
|
261
|
+
* manifest is the runtime index the embed fetches; the clips are what it
|
|
262
|
+
* references by relative src. Cache files never ship.
|
|
209
263
|
*/
|
|
210
|
-
async function
|
|
211
|
-
|
|
212
|
-
|
|
264
|
+
async function copyClipTrack(
|
|
265
|
+
scenarioDir: string,
|
|
266
|
+
outDir: string,
|
|
267
|
+
trackDir: string,
|
|
268
|
+
extension: string,
|
|
269
|
+
): Promise<void> {
|
|
270
|
+
const srcDir = join(scenarioDir, trackDir);
|
|
271
|
+
const destDir = join(outDir, trackDir);
|
|
213
272
|
await mkdir(destDir, { recursive: true });
|
|
214
273
|
const entries = await readdir(srcDir, { withFileTypes: true });
|
|
215
274
|
for (const entry of entries) {
|
|
216
275
|
if (!entry.isFile()) continue;
|
|
217
276
|
const name = entry.name.toLowerCase();
|
|
218
|
-
if (name === "manifest.json" || name.endsWith(
|
|
277
|
+
if (name === "manifest.json" || name.endsWith(extension)) {
|
|
219
278
|
await copyFile(join(srcDir, entry.name), join(destDir, entry.name));
|
|
220
279
|
}
|
|
221
280
|
}
|
|
222
281
|
}
|
|
223
282
|
|
|
283
|
+
/**
|
|
284
|
+
* Copy the soundtrack's audio into the bundle: the authored music file at
|
|
285
|
+
* its scenario-relative path (the embed references it by that same path),
|
|
286
|
+
* and the built-in SFX set from @scenar/react at the shared
|
|
287
|
+
* {@link SFX_DEST_PATHS} locations the generated entry points at. Returns
|
|
288
|
+
* the number of files copied. Remote music URLs copy nothing.
|
|
289
|
+
*/
|
|
290
|
+
async function copySoundtrack(
|
|
291
|
+
scenarioDir: string,
|
|
292
|
+
outDir: string,
|
|
293
|
+
soundtrack: AuthoredSoundtrack,
|
|
294
|
+
): Promise<number> {
|
|
295
|
+
let copied = 0;
|
|
296
|
+
|
|
297
|
+
if (soundtrack.musicSrc) {
|
|
298
|
+
const music = await resolveMusicAsset(scenarioDir, soundtrack.musicSrc);
|
|
299
|
+
if (music) {
|
|
300
|
+
const dest = join(outDir, music.destRelPath);
|
|
301
|
+
await mkdir(dirname(dest), { recursive: true });
|
|
302
|
+
await copyFile(music.sourcePath, dest);
|
|
303
|
+
copied += 1;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (soundtrack.sfx) {
|
|
308
|
+
const sfxPaths = resolveSfxAssetPaths(scenarioDir);
|
|
309
|
+
for (const sound of ["click", "keystroke"] as const) {
|
|
310
|
+
const dest = join(outDir, SFX_DEST_PATHS[sound]);
|
|
311
|
+
await mkdir(dirname(dest), { recursive: true });
|
|
312
|
+
await copyFile(sfxPaths[sound], dest);
|
|
313
|
+
copied += 1;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return copied;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Copy title-card logos into the bundle at their scenario-relative paths.
|
|
322
|
+
* A logo shared by intro and outro copies once; remote URLs copy nothing.
|
|
323
|
+
* Returns the number of files copied.
|
|
324
|
+
*/
|
|
325
|
+
async function copyTitleCardLogos(
|
|
326
|
+
scenarioDir: string,
|
|
327
|
+
outDir: string,
|
|
328
|
+
titleCards: AuthoredTitleCards,
|
|
329
|
+
): Promise<number> {
|
|
330
|
+
let copied = 0;
|
|
331
|
+
const copiedPaths = new Set<string>();
|
|
332
|
+
|
|
333
|
+
for (const side of ["intro", "outro"] as const) {
|
|
334
|
+
const logoSrc = titleCards[side]?.logoSrc;
|
|
335
|
+
if (!logoSrc) continue;
|
|
336
|
+
const logo = await resolveLogoAsset(scenarioDir, logoSrc, `titleCards.${side}.logoSrc`);
|
|
337
|
+
if (!logo || copiedPaths.has(logo.destRelPath)) continue;
|
|
338
|
+
const dest = join(outDir, logo.destRelPath);
|
|
339
|
+
await mkdir(dirname(dest), { recursive: true });
|
|
340
|
+
await copyFile(logo.sourcePath, dest);
|
|
341
|
+
copiedPaths.add(logo.destRelPath);
|
|
342
|
+
copied += 1;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return copied;
|
|
346
|
+
}
|
|
347
|
+
|
|
224
348
|
/** One log-friendly line for the shot discovery outcome. */
|
|
225
349
|
function describeCollectedShots(collected: CollectedPackShots): string {
|
|
226
350
|
if (!collected.recorded) {
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal HeyGen API client for `scenar presenter` — deliberately an
|
|
3
|
+
* internal module of the CLI, not published API. Shaped so a second
|
|
4
|
+
* avatar provider could slot in behind the same function signatures,
|
|
5
|
+
* but the interface is not exported until a second provider is a
|
|
6
|
+
* concrete need (the TTS-provider precedent).
|
|
7
|
+
*
|
|
8
|
+
* Endpoints and response fields follow the live Gate 0 transcript:
|
|
9
|
+
* `POST /v3/assets` (multipart) → `data.asset_id`;
|
|
10
|
+
* `POST /v3/videos` (`type: "avatar"`, `audio_asset_id`) →
|
|
11
|
+
* `data.video_id`; `GET /v3/videos/{id}` → `data.status`
|
|
12
|
+
* (`completed`/`failed`), `data.video_url`, `data.failure_message`.
|
|
13
|
+
*
|
|
14
|
+
* BYO key, no Scenar-side proxying — identical to the TTS providers.
|
|
15
|
+
* Native fetch, no new runtime dependencies.
|
|
16
|
+
*/
|
|
17
|
+
/** Engine flag values → HeyGen's API vocabulary. */
|
|
18
|
+
export declare const HEYGEN_ENGINE_TYPES: {
|
|
19
|
+
readonly iii: "avatar_iii";
|
|
20
|
+
readonly iv: "avatar_iv";
|
|
21
|
+
};
|
|
22
|
+
export type HeygenEngineFlag = keyof typeof HEYGEN_ENGINE_TYPES;
|
|
23
|
+
/**
|
|
24
|
+
* Indicative $/minute of narration audio per engine, from the project
|
|
25
|
+
* cost analysis. The estimate the cost gate prints is indicative —
|
|
26
|
+
* HeyGen bills credits, not dollars.
|
|
27
|
+
*/
|
|
28
|
+
export declare const HEYGEN_RATE_PER_MIN: Record<HeygenEngineFlag, number>;
|
|
29
|
+
export interface HeygenRequestContext {
|
|
30
|
+
readonly apiKey: string;
|
|
31
|
+
/** Injectable for tests; defaults to a real timer. */
|
|
32
|
+
readonly sleep?: (ms: number) => Promise<void>;
|
|
33
|
+
/** Rate-limit feedback sink (stderr line per retry). */
|
|
34
|
+
readonly onRetry?: (message: string) => void;
|
|
35
|
+
}
|
|
36
|
+
/** Upload one narration MP3 as a HeyGen asset (free). Returns the asset id. */
|
|
37
|
+
export declare function uploadAudioAsset(ctx: HeygenRequestContext, audio: Buffer, filename: string): Promise<string>;
|
|
38
|
+
export interface CreateAvatarVideoInput {
|
|
39
|
+
readonly avatarId: string;
|
|
40
|
+
readonly audioAssetId: string;
|
|
41
|
+
readonly engine: HeygenEngineFlag;
|
|
42
|
+
readonly resolution: string;
|
|
43
|
+
/**
|
|
44
|
+
* Deterministic title (`scenar <scenarioId> step-<N> <hash8>`) so any
|
|
45
|
+
* duplicate generation is identifiable in the HeyGen dashboard.
|
|
46
|
+
*/
|
|
47
|
+
readonly title: string;
|
|
48
|
+
}
|
|
49
|
+
/** Create one avatar video lip-synced to the uploaded audio. Returns the video id. */
|
|
50
|
+
export declare function createAvatarVideo(ctx: HeygenRequestContext, input: CreateAvatarVideoInput): Promise<string>;
|
|
51
|
+
export interface PollOutcome {
|
|
52
|
+
readonly status: "completed" | "failed" | "timeout";
|
|
53
|
+
readonly videoUrl?: string;
|
|
54
|
+
readonly failureMessage?: string;
|
|
55
|
+
}
|
|
56
|
+
export interface PollOptions {
|
|
57
|
+
readonly intervalMs: number;
|
|
58
|
+
readonly timeoutMs: number;
|
|
59
|
+
/** Elapsed-time feedback per tick (drives the progress line). */
|
|
60
|
+
readonly onTick?: (elapsedMs: number) => void;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Poll a generation to completion. Polling is read-only, so transient
|
|
64
|
+
* network errors retry on the next tick inside the step timeout.
|
|
65
|
+
*/
|
|
66
|
+
export declare function pollVideo(ctx: HeygenRequestContext, videoId: string, options: PollOptions): Promise<PollOutcome>;
|
|
67
|
+
/** Download a completed clip. Returns the raw bytes. */
|
|
68
|
+
export declare function downloadVideo(url: string): Promise<Buffer>;
|
|
69
|
+
//# sourceMappingURL=heygen-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"heygen-client.d.ts","sourceRoot":"","sources":["../../../src/presenter/heygen-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,oDAAoD;AACpD,eAAO,MAAM,mBAAmB;;;CAGtB,CAAC;AAEX,MAAM,MAAM,gBAAgB,GAAG,MAAM,OAAO,mBAAmB,CAAC;AAEhE;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,gBAAgB,EAAE,MAAM,CAGhE,CAAC;AAKF,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,sDAAsD;IACtD,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,wDAAwD;IACxD,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9C;AA6CD,+EAA+E;AAC/E,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,oBAAoB,EACzB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,CAAC,CAkBjB;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAClC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B;;;OAGG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,sFAAsF;AACtF,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,oBAAoB,EACzB,KAAK,EAAE,sBAAsB,GAC5B,OAAO,CAAC,MAAM,CAAC,CAsBjB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,MAAM,EAAE,WAAW,GAAG,QAAQ,GAAG,SAAS,CAAC;IACpD,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CAClC;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,iEAAiE;IACjE,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/C;AAED;;;GAGG;AACH,wBAAsB,SAAS,CAC7B,GAAG,EAAE,oBAAoB,EACzB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,WAAW,CAAC,CAqCtB;AAED,wDAAwD;AACxD,wBAAsB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAMhE"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal HeyGen API client for `scenar presenter` — deliberately an
|
|
3
|
+
* internal module of the CLI, not published API. Shaped so a second
|
|
4
|
+
* avatar provider could slot in behind the same function signatures,
|
|
5
|
+
* but the interface is not exported until a second provider is a
|
|
6
|
+
* concrete need (the TTS-provider precedent).
|
|
7
|
+
*
|
|
8
|
+
* Endpoints and response fields follow the live Gate 0 transcript:
|
|
9
|
+
* `POST /v3/assets` (multipart) → `data.asset_id`;
|
|
10
|
+
* `POST /v3/videos` (`type: "avatar"`, `audio_asset_id`) →
|
|
11
|
+
* `data.video_id`; `GET /v3/videos/{id}` → `data.status`
|
|
12
|
+
* (`completed`/`failed`), `data.video_url`, `data.failure_message`.
|
|
13
|
+
*
|
|
14
|
+
* BYO key, no Scenar-side proxying — identical to the TTS providers.
|
|
15
|
+
* Native fetch, no new runtime dependencies.
|
|
16
|
+
*/
|
|
17
|
+
const HEYGEN_BASE_URL = "https://api.heygen.com";
|
|
18
|
+
/** Engine flag values → HeyGen's API vocabulary. */
|
|
19
|
+
export const HEYGEN_ENGINE_TYPES = {
|
|
20
|
+
iii: "avatar_iii",
|
|
21
|
+
iv: "avatar_iv",
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Indicative $/minute of narration audio per engine, from the project
|
|
25
|
+
* cost analysis. The estimate the cost gate prints is indicative —
|
|
26
|
+
* HeyGen bills credits, not dollars.
|
|
27
|
+
*/
|
|
28
|
+
export const HEYGEN_RATE_PER_MIN = {
|
|
29
|
+
iii: 1.0,
|
|
30
|
+
iv: 4.0,
|
|
31
|
+
};
|
|
32
|
+
const MAX_ATTEMPTS_ON_429 = 3;
|
|
33
|
+
const DEFAULT_RETRY_AFTER_S = 30;
|
|
34
|
+
const realSleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
35
|
+
/**
|
|
36
|
+
* Run a HeyGen request, honoring 429 rate limits: wait `Retry-After`
|
|
37
|
+
* (default 30 s) and retry the same call, up to 3 attempts total.
|
|
38
|
+
*/
|
|
39
|
+
async function heygenFetch(ctx, path, init) {
|
|
40
|
+
const sleep = ctx.sleep ?? realSleep;
|
|
41
|
+
let response;
|
|
42
|
+
for (let attempt = 1;; attempt++) {
|
|
43
|
+
response = await fetch(`${HEYGEN_BASE_URL}${path}`, {
|
|
44
|
+
...init,
|
|
45
|
+
headers: { "x-api-key": ctx.apiKey, ...(init.headers ?? {}) },
|
|
46
|
+
});
|
|
47
|
+
if (response.status !== 429 || attempt >= MAX_ATTEMPTS_ON_429)
|
|
48
|
+
return response;
|
|
49
|
+
const retryAfterS = Number(response.headers.get("retry-after")) || DEFAULT_RETRY_AFTER_S;
|
|
50
|
+
ctx.onRetry?.(`rate-limited, retrying in ${retryAfterS}s (${attempt}/${MAX_ATTEMPTS_ON_429})`);
|
|
51
|
+
await sleep(retryAfterS * 1000);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async function readBody(response) {
|
|
55
|
+
try {
|
|
56
|
+
return (await response.json());
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return {};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function apiError(operation, response, body) {
|
|
63
|
+
return new Error(`HeyGen ${operation} failed (${response.status}): ${JSON.stringify(body)}`);
|
|
64
|
+
}
|
|
65
|
+
/** Upload one narration MP3 as a HeyGen asset (free). Returns the asset id. */
|
|
66
|
+
export async function uploadAudioAsset(ctx, audio, filename) {
|
|
67
|
+
const form = new FormData();
|
|
68
|
+
form.append("file", new Blob([new Uint8Array(audio)], { type: "audio/mpeg" }), filename);
|
|
69
|
+
const response = await heygenFetch(ctx, "/v3/assets", {
|
|
70
|
+
method: "POST",
|
|
71
|
+
body: form,
|
|
72
|
+
});
|
|
73
|
+
const body = await readBody(response);
|
|
74
|
+
if (!response.ok)
|
|
75
|
+
throw apiError("asset upload", response, body);
|
|
76
|
+
const assetId = body["data"]?.["asset_id"];
|
|
77
|
+
if (typeof assetId !== "string" || assetId.length === 0) {
|
|
78
|
+
throw new Error(`HeyGen asset upload returned no asset_id: ${JSON.stringify(body)}`);
|
|
79
|
+
}
|
|
80
|
+
return assetId;
|
|
81
|
+
}
|
|
82
|
+
/** Create one avatar video lip-synced to the uploaded audio. Returns the video id. */
|
|
83
|
+
export async function createAvatarVideo(ctx, input) {
|
|
84
|
+
const response = await heygenFetch(ctx, "/v3/videos", {
|
|
85
|
+
method: "POST",
|
|
86
|
+
headers: { "Content-Type": "application/json" },
|
|
87
|
+
body: JSON.stringify({
|
|
88
|
+
type: "avatar",
|
|
89
|
+
avatar_id: input.avatarId,
|
|
90
|
+
audio_asset_id: input.audioAssetId,
|
|
91
|
+
engine: { type: HEYGEN_ENGINE_TYPES[input.engine] },
|
|
92
|
+
output_format: "mp4",
|
|
93
|
+
aspect_ratio: "auto",
|
|
94
|
+
resolution: input.resolution,
|
|
95
|
+
title: input.title,
|
|
96
|
+
}),
|
|
97
|
+
});
|
|
98
|
+
const body = await readBody(response);
|
|
99
|
+
if (!response.ok)
|
|
100
|
+
throw apiError("video create", response, body);
|
|
101
|
+
const videoId = body["data"]?.["video_id"];
|
|
102
|
+
if (typeof videoId !== "string" || videoId.length === 0) {
|
|
103
|
+
throw new Error(`HeyGen video create returned no video_id: ${JSON.stringify(body)}`);
|
|
104
|
+
}
|
|
105
|
+
return videoId;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Poll a generation to completion. Polling is read-only, so transient
|
|
109
|
+
* network errors retry on the next tick inside the step timeout.
|
|
110
|
+
*/
|
|
111
|
+
export async function pollVideo(ctx, videoId, options) {
|
|
112
|
+
const sleep = ctx.sleep ?? realSleep;
|
|
113
|
+
const startedAt = Date.now();
|
|
114
|
+
while (Date.now() - startedAt < options.timeoutMs) {
|
|
115
|
+
await sleep(options.intervalMs);
|
|
116
|
+
options.onTick?.(Date.now() - startedAt);
|
|
117
|
+
let body;
|
|
118
|
+
try {
|
|
119
|
+
const response = await heygenFetch(ctx, `/v3/videos/${videoId}`, { method: "GET" });
|
|
120
|
+
body = await readBody(response);
|
|
121
|
+
if (!response.ok)
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
const data = (body["data"] ?? {});
|
|
128
|
+
const status = data["status"];
|
|
129
|
+
if (status === "completed") {
|
|
130
|
+
return {
|
|
131
|
+
status: "completed",
|
|
132
|
+
videoUrl: typeof data["video_url"] === "string" ? data["video_url"] : undefined,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
if (status === "failed") {
|
|
136
|
+
return {
|
|
137
|
+
status: "failed",
|
|
138
|
+
failureMessage: typeof data["failure_message"] === "string"
|
|
139
|
+
? data["failure_message"]
|
|
140
|
+
: JSON.stringify(data["failure_message"] ?? "unknown failure"),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return { status: "timeout" };
|
|
145
|
+
}
|
|
146
|
+
/** Download a completed clip. Returns the raw bytes. */
|
|
147
|
+
export async function downloadVideo(url) {
|
|
148
|
+
const response = await fetch(url);
|
|
149
|
+
if (!response.ok) {
|
|
150
|
+
throw new Error(`HeyGen clip download failed (${response.status}) for ${url}`);
|
|
151
|
+
}
|
|
152
|
+
return Buffer.from(await response.arrayBuffer());
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=heygen-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"heygen-client.js","sourceRoot":"","sources":["../../../src/presenter/heygen-client.ts"],"names":[],"mappings":"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"}
|