dsh-hyperframes 0.10.2 → 0.11.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/CHANGELOG.md +7 -0
- package/README.en.md +2 -1
- package/README.md +2 -1
- package/docs/WORKBENCH.md +6 -0
- package/lib/client.js +40 -2
- package/lib/media-templates.d.ts +1 -1
- package/lib/media-templates.js +1 -1
- package/package.json +2 -2
- package/plugin.json +1 -1
- package/skills/NOTICE.md +1 -1
- package/skills/UPSTREAM.json +93 -8
- package/skills/embedded-captions/SKILL.md +1 -1
- package/skills/faceless-explainer/SKILL.md +1 -1
- package/skills/general-video/SKILL.md +1 -1
- package/skills/hyperframes-animation/SKILL.md +1 -1
- package/skills/hyperframes-audio/SKILL.md +1 -1
- package/skills/hyperframes-core/SKILL.md +1 -1
- package/skills/hyperframes-creative/SKILL.md +1 -1
- package/skills/hyperframes-keyframes/SKILL.md +1 -1
- package/skills/hyperframes-studio/SKILL.md +1 -1
- package/skills/media-use/scripts/lib/adopt.mjs +128 -0
- package/skills/media-use/scripts/lib/adopt.test.mjs +95 -0
- package/skills/media-use/scripts/lib/bgm-provider.mjs +20 -0
- package/skills/media-use/scripts/lib/brand-provider.mjs +59 -0
- package/skills/media-use/scripts/lib/bundled-sfx-provider.mjs +119 -0
- package/skills/media-use/scripts/lib/bundled-sfx-provider.test.mjs +139 -0
- package/skills/media-use/scripts/lib/cache.mjs +162 -0
- package/skills/media-use/scripts/lib/cache.test.mjs +110 -0
- package/skills/media-use/scripts/lib/candidates.mjs +91 -0
- package/skills/media-use/scripts/lib/candidates.test.mjs +155 -0
- package/skills/media-use/scripts/lib/codex-provider.mjs +161 -0
- package/skills/media-use/scripts/lib/codex-provider.test.mjs +34 -0
- package/skills/media-use/scripts/lib/config-lock.d.mts +3 -0
- package/skills/media-use/scripts/lib/config-lock.test.mjs +79 -0
- package/skills/media-use/scripts/lib/coverage.test.mjs +136 -0
- package/skills/media-use/scripts/lib/cube-build.mjs +181 -0
- package/skills/media-use/scripts/lib/cube-build.test.mjs +80 -0
- package/skills/media-use/scripts/lib/cube-validate.mjs +197 -0
- package/skills/media-use/scripts/lib/cube-validate.test.mjs +125 -0
- package/skills/media-use/scripts/lib/cutlist.test.mjs +174 -0
- package/skills/media-use/scripts/lib/duck.test.mjs +319 -0
- package/skills/media-use/scripts/lib/error-diffusion.test.mjs +102 -0
- package/skills/media-use/scripts/lib/ff-binaries.mjs +37 -0
- package/skills/media-use/scripts/lib/freeze.mjs +72 -0
- package/skills/media-use/scripts/lib/freeze.test.mjs +138 -0
- package/skills/media-use/scripts/lib/grade-analyzer.mjs +277 -0
- package/skills/media-use/scripts/lib/grade-analyzer.test.mjs +285 -0
- package/skills/media-use/scripts/lib/heygen-cli.mjs +208 -0
- package/skills/media-use/scripts/lib/heygen-cli.test.mjs +315 -0
- package/skills/media-use/scripts/lib/heygen-search.mjs +45 -0
- package/skills/media-use/scripts/lib/heygen-search.test.mjs +49 -0
- package/skills/media-use/scripts/lib/heygen-video-provider.mjs +138 -0
- package/skills/media-use/scripts/lib/heygen-video-provider.test.mjs +435 -0
- package/skills/media-use/scripts/lib/image-provider.mjs +44 -0
- package/skills/media-use/scripts/lib/local-media-search.mjs +32 -0
- package/skills/media-use/scripts/lib/local-models.mjs +318 -0
- package/skills/media-use/scripts/lib/local-models.test.mjs +220 -0
- package/skills/media-use/scripts/lib/local-run.mjs +78 -0
- package/skills/media-use/scripts/lib/local-run.test.mjs +121 -0
- package/skills/media-use/scripts/lib/logo-provider.mjs +244 -0
- package/skills/media-use/scripts/lib/logo-provider.test.mjs +298 -0
- package/skills/media-use/scripts/lib/ltx-video-provider.mjs +94 -0
- package/skills/media-use/scripts/lib/ltx-video-provider.test.mjs +316 -0
- package/skills/media-use/scripts/lib/lut-preset-provider.mjs +271 -0
- package/skills/media-use/scripts/lib/lut-preset-provider.test.mjs +199 -0
- package/skills/media-use/scripts/lib/main-module.mjs +13 -0
- package/skills/media-use/scripts/lib/manifest.d.mts +20 -0
- package/skills/media-use/scripts/lib/manifest.test.mjs +397 -0
- package/skills/media-use/scripts/lib/match.mjs +47 -0
- package/skills/media-use/scripts/lib/media-fetch.test.mjs +197 -0
- package/skills/media-use/scripts/lib/media-home.test.mjs +61 -0
- package/skills/media-use/scripts/lib/media-search.d.mts +9 -0
- package/skills/media-use/scripts/lib/media-search.mjs +107 -0
- package/skills/media-use/scripts/lib/mflux-provider.mjs +128 -0
- package/skills/media-use/scripts/lib/mflux-provider.test.mjs +174 -0
- package/skills/media-use/scripts/lib/misses.mjs +49 -0
- package/skills/media-use/scripts/lib/misses.test.mjs +89 -0
- package/skills/media-use/scripts/lib/npx-sync.mjs +2 -28
- package/skills/media-use/scripts/lib/npx-sync.test.mjs +50 -0
- package/skills/media-use/scripts/lib/openverse.mjs +134 -0
- package/skills/media-use/scripts/lib/openverse.test.mjs +219 -0
- package/skills/media-use/scripts/lib/parakeet-words.test.mjs +44 -0
- package/skills/media-use/scripts/lib/prefs-store.concurrent.test.mjs +173 -0
- package/skills/media-use/scripts/lib/prefs-store.test.mjs +245 -0
- package/skills/media-use/scripts/lib/probe.mjs +46 -0
- package/skills/media-use/scripts/lib/probe.test.mjs +221 -0
- package/skills/media-use/scripts/lib/providers.mjs +5 -0
- package/skills/media-use/scripts/lib/recipe-store.test.mjs +391 -0
- package/skills/media-use/scripts/lib/registry.mjs +214 -0
- package/skills/media-use/scripts/lib/registry.test.mjs +262 -0
- package/skills/media-use/scripts/lib/search.mjs +24 -0
- package/skills/media-use/scripts/lib/search.test.mjs +32 -0
- package/skills/media-use/scripts/lib/sfx-provider.mjs +33 -0
- package/skills/media-use/scripts/lib/specs.mjs +132 -0
- package/skills/media-use/scripts/lib/specs.test.mjs +157 -0
- package/skills/media-use/scripts/lib/stats.mjs +117 -0
- package/skills/media-use/scripts/lib/stats.test.mjs +165 -0
- package/skills/media-use/scripts/lib/svg-sanitize.mjs +21 -0
- package/skills/media-use/scripts/lib/telemetry.test.mjs +437 -53
- package/skills/media-use/scripts/lib/test-home.test.mjs +10 -0
- package/skills/media-use/scripts/lib/transcriptCutFade.test.mjs +33 -0
- package/skills/media-use/scripts/lib/tts-local-provider.mjs +94 -0
- package/skills/media-use/scripts/lib/tts-local-provider.test.mjs +72 -0
- package/skills/media-use/scripts/lib/usage.mjs +57 -0
- package/skills/media-use/scripts/lib/usage.test.mjs +54 -0
- package/skills/media-use/scripts/lib/voice-provider.mjs +57 -0
- package/skills/media-use/scripts/lib/voice-provider.test.mjs +46 -0
- package/skills/media-use/scripts/resolve.mjs +1360 -27
- package/skills/motion-graphics/SKILL.md +1 -1
- package/skills/music-to-video/SKILL.md +1 -1
- package/skills/pr-to-video/SKILL.md +1 -1
- package/skills/product-launch-video/SKILL.md +1 -1
- package/skills/talking-head-recut/SKILL.md +1 -1
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { localTtsGenerate } from "./tts-local-provider.mjs";
|
|
4
|
+
|
|
5
|
+
// Regression: on Windows, a bare "npx" is npx.cmd, which execFileSync cannot
|
|
6
|
+
// exec — the Kokoro delegation failed with `spawnSync npx ENOENT` instead of
|
|
7
|
+
// synthesizing (or cleanly falling through to the next provider). The spawn
|
|
8
|
+
// must route through node + npx-cli.js on win32, same as the audio engine's
|
|
9
|
+
// TTS spawns (see ../../audio/scripts/lib/tts.spawn.test.mjs).
|
|
10
|
+
|
|
11
|
+
const envWithNpxCli = {
|
|
12
|
+
npm_execpath: "C:/Program Files/nodejs/node_modules/npm/bin/npm-cli.js",
|
|
13
|
+
npm_node_execpath: "C:/Program Files/nodejs/node.exe",
|
|
14
|
+
};
|
|
15
|
+
const npxCliPath = "C:/Program Files/nodejs/node_modules/npm/bin/npx-cli.js";
|
|
16
|
+
const pathExists = (path) => path === npxCliPath;
|
|
17
|
+
|
|
18
|
+
test("win32: routes the hyperframes tts call through node + npx-cli, never bare npx", async () => {
|
|
19
|
+
const captured = [];
|
|
20
|
+
const fakeExec = (cmd, args, opts) => {
|
|
21
|
+
captured.push({ cmd, args, opts });
|
|
22
|
+
// Synthesize nothing — the provider then returns null via the
|
|
23
|
+
// missing-output check, which is fine: we only assert the spawn shape.
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
await localTtsGenerate(
|
|
27
|
+
"hello there",
|
|
28
|
+
{ voice: "am_michael" },
|
|
29
|
+
"win32",
|
|
30
|
+
fakeExec,
|
|
31
|
+
envWithNpxCli,
|
|
32
|
+
pathExists,
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
assert.equal(captured.length, 1);
|
|
36
|
+
assert.equal(captured[0].cmd, envWithNpxCli.npm_node_execpath);
|
|
37
|
+
assert.equal(captured[0].args[0], npxCliPath);
|
|
38
|
+
assert.deepEqual(captured[0].args.slice(1, 4), ["hyperframes", "tts", "hello there"]);
|
|
39
|
+
assert.ok(captured[0].args.includes("--voice"));
|
|
40
|
+
// execFileSync options survive the rerouting (pipes are what let the caller
|
|
41
|
+
// read the "kokoro-onnx not installed" hint back out).
|
|
42
|
+
assert.deepEqual(captured[0].opts.stdio, ["ignore", "pipe", "pipe"]);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("win32 without a resolvable npx-cli: falls through to the next provider (null), no spawn", async () => {
|
|
46
|
+
const captured = [];
|
|
47
|
+
const fakeExec = (...call) => captured.push(call);
|
|
48
|
+
|
|
49
|
+
const result = await localTtsGenerate(
|
|
50
|
+
"hello",
|
|
51
|
+
{},
|
|
52
|
+
"win32",
|
|
53
|
+
fakeExec,
|
|
54
|
+
{}, // no npm_execpath, and pathExists finds nothing
|
|
55
|
+
() => false,
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
assert.equal(result, null);
|
|
59
|
+
assert.equal(captured.length, 0);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("non-win32: spawns plain npx unchanged", async () => {
|
|
63
|
+
const captured = [];
|
|
64
|
+
const fakeExec = (cmd, args) => captured.push({ cmd, args });
|
|
65
|
+
|
|
66
|
+
await localTtsGenerate("hola", { lang: "es" }, "darwin", fakeExec, {}, () => false);
|
|
67
|
+
|
|
68
|
+
assert.equal(captured.length, 1);
|
|
69
|
+
assert.equal(captured[0].cmd, "npx");
|
|
70
|
+
assert.deepEqual(captured[0].args.slice(0, 3), ["hyperframes", "tts", "hola"]);
|
|
71
|
+
assert.ok(captured[0].args.includes("--lang"));
|
|
72
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { readFileSync, readdirSync, existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
// Asset in-use detection: which manifest assets are actually referenced by the
|
|
5
|
+
// project's compositions. Answers "is this safe to prune?" from the CLI. (The
|
|
6
|
+
// Studio Asset tab computes its in-use filter separately from the live timeline;
|
|
7
|
+
// this is the skill-side equivalent for headless use.)
|
|
8
|
+
//
|
|
9
|
+
// ponytail: substring match of each asset's filename against the .html text
|
|
10
|
+
// (covers src= / href= / url() / data-* without parsing HTML). False positives
|
|
11
|
+
// only if a filename literally appears in prose; fine for a filter. Upgrade to
|
|
12
|
+
// attribute parsing if that ever bites.
|
|
13
|
+
|
|
14
|
+
function compositionHtml(projectDir) {
|
|
15
|
+
let files = [];
|
|
16
|
+
try {
|
|
17
|
+
files = readdirSync(projectDir).filter((f) => f.endsWith(".html"));
|
|
18
|
+
} catch {
|
|
19
|
+
return "";
|
|
20
|
+
}
|
|
21
|
+
const sub = join(projectDir, "compositions");
|
|
22
|
+
if (existsSync(sub)) {
|
|
23
|
+
try {
|
|
24
|
+
for (const f of readdirSync(sub))
|
|
25
|
+
if (f.endsWith(".html")) files.push(join("compositions", f));
|
|
26
|
+
} catch {
|
|
27
|
+
// ignore unreadable subdir
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return files
|
|
31
|
+
.map((f) => {
|
|
32
|
+
try {
|
|
33
|
+
return readFileSync(join(projectDir, f), "utf8");
|
|
34
|
+
} catch {
|
|
35
|
+
return "";
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
.join("\n");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Tag each record with `inUse` (referenced by some composition). */
|
|
42
|
+
export function tagUsage(records, projectDir) {
|
|
43
|
+
const html = compositionHtml(projectDir);
|
|
44
|
+
return records.map((r) => {
|
|
45
|
+
const file = r.path ? r.path.split("/").pop() : r.id;
|
|
46
|
+
return { ...r, inUse: Boolean(file) && html.includes(file) };
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Split records into { used, unused } for the filter. */
|
|
51
|
+
export function partitionUsage(records, projectDir) {
|
|
52
|
+
const tagged = tagUsage(records, projectDir);
|
|
53
|
+
return {
|
|
54
|
+
used: tagged.filter((r) => r.inUse),
|
|
55
|
+
unused: tagged.filter((r) => !r.inUse),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { strict as assert } from "node:assert";
|
|
2
|
+
import { test } from "node:test";
|
|
3
|
+
import { mkdtempSync, writeFileSync, rmSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { tagUsage, partitionUsage } from "./usage.mjs";
|
|
7
|
+
|
|
8
|
+
function project(html) {
|
|
9
|
+
const dir = mkdtempSync(join(tmpdir(), "mu-usage-"));
|
|
10
|
+
writeFileSync(join(dir, "index.html"), html);
|
|
11
|
+
return dir;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const records = [
|
|
15
|
+
{ id: "bgm_001", path: ".media/audio/bgm/bgm_001.wav", description: "used track" },
|
|
16
|
+
{ id: "bgm_002", path: ".media/audio/bgm/bgm_002.wav", description: "orphan track" },
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
test("an asset referenced by a composition is marked in-use", () => {
|
|
20
|
+
const dir = project(`<audio src="assets/bgm_001.wav"></audio>`);
|
|
21
|
+
try {
|
|
22
|
+
const tagged = tagUsage(records, dir);
|
|
23
|
+
assert.equal(tagged.find((r) => r.id === "bgm_001").inUse, true);
|
|
24
|
+
assert.equal(tagged.find((r) => r.id === "bgm_002").inUse, false);
|
|
25
|
+
} finally {
|
|
26
|
+
rmSync(dir, { recursive: true, force: true });
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("partitionUsage splits used vs unused for the filter", () => {
|
|
31
|
+
const dir = project(`<img src="bgm_001.wav">`);
|
|
32
|
+
try {
|
|
33
|
+
const { used, unused } = partitionUsage(records, dir);
|
|
34
|
+
assert.deepEqual(
|
|
35
|
+
used.map((r) => r.id),
|
|
36
|
+
["bgm_001"],
|
|
37
|
+
);
|
|
38
|
+
assert.deepEqual(
|
|
39
|
+
unused.map((r) => r.id),
|
|
40
|
+
["bgm_002"],
|
|
41
|
+
);
|
|
42
|
+
} finally {
|
|
43
|
+
rmSync(dir, { recursive: true, force: true });
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("no compositions -> everything reads as unused (safe default)", () => {
|
|
48
|
+
const dir = mkdtempSync(join(tmpdir(), "mu-usage-"));
|
|
49
|
+
try {
|
|
50
|
+
assert.equal(partitionUsage(records, dir).used.length, 0);
|
|
51
|
+
} finally {
|
|
52
|
+
rmSync(dir, { recursive: true, force: true });
|
|
53
|
+
}
|
|
54
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { HEYGEN_CLIENT_SOURCE_ARGV, runHeygenJson } from "./heygen-cli.mjs";
|
|
2
|
+
|
|
3
|
+
// Voice / TTS generation via the HeyGen CLI — the only external CLI media-use
|
|
4
|
+
// shells (CLI-only invariant: media-use holds no keys; the CLI owns auth).
|
|
5
|
+
// Flags verified against `heygen voice speech create --help` (v0.3.0).
|
|
6
|
+
|
|
7
|
+
function result(url, duration, provider, intent) {
|
|
8
|
+
if (!url) return null;
|
|
9
|
+
return {
|
|
10
|
+
url,
|
|
11
|
+
source: "generated",
|
|
12
|
+
metadata: {
|
|
13
|
+
description: intent,
|
|
14
|
+
provider,
|
|
15
|
+
...(duration != null && { duration }),
|
|
16
|
+
provenance: { prompt: intent },
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// HeyGen TTS requires a starfish-engine voice. Default to the first one the
|
|
22
|
+
// catalog returns (deterministic order); pass ctx.voiceId to override.
|
|
23
|
+
// ponytail: listed once per process; the resolved asset is frozen + cached after
|
|
24
|
+
// first use, so the network list only happens on a cache miss. Cache only a
|
|
25
|
+
// truthy id -- a transient list failure must not poison the cache with `null`
|
|
26
|
+
// and permanently disable TTS for the rest of the process.
|
|
27
|
+
let cachedVoiceId;
|
|
28
|
+
function defaultVoiceId() {
|
|
29
|
+
if (cachedVoiceId) return cachedVoiceId;
|
|
30
|
+
const j = runHeygenJson(
|
|
31
|
+
"heygen",
|
|
32
|
+
["voice", "list", "--engine", "starfish", "--limit", "1"],
|
|
33
|
+
"voice list",
|
|
34
|
+
);
|
|
35
|
+
cachedVoiceId = j?.data?.[0]?.voice_id || null;
|
|
36
|
+
return cachedVoiceId;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function heygenTtsGenerate(intent, ctx) {
|
|
40
|
+
const voiceId = ctx?.voiceId || defaultVoiceId();
|
|
41
|
+
if (!voiceId) return null;
|
|
42
|
+
const p = runHeygenJson(
|
|
43
|
+
"heygen",
|
|
44
|
+
[
|
|
45
|
+
...HEYGEN_CLIENT_SOURCE_ARGV,
|
|
46
|
+
"voice",
|
|
47
|
+
"speech",
|
|
48
|
+
"create",
|
|
49
|
+
"--text",
|
|
50
|
+
intent,
|
|
51
|
+
"--voice-id",
|
|
52
|
+
voiceId,
|
|
53
|
+
],
|
|
54
|
+
"tts",
|
|
55
|
+
);
|
|
56
|
+
return result(p?.data?.audio_url, p?.data?.duration, "heygen.tts", intent);
|
|
57
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { strict as assert } from "node:assert";
|
|
2
|
+
import { chmodSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { test } from "node:test";
|
|
6
|
+
import { heygenTtsGenerate } from "./voice-provider.mjs";
|
|
7
|
+
|
|
8
|
+
test("tags TTS generation but not voice discovery with the media-use client source", async () => {
|
|
9
|
+
const dir = mkdtempSync(join(tmpdir(), "media-use-voice-provider-"));
|
|
10
|
+
const capturePath = join(dir, "argv.log");
|
|
11
|
+
const heygenPath = join(dir, "heygen");
|
|
12
|
+
const previousPath = process.env.PATH;
|
|
13
|
+
const previousCapturePath = process.env.HEYGEN_CAPTURE_PATH;
|
|
14
|
+
|
|
15
|
+
writeFileSync(
|
|
16
|
+
heygenPath,
|
|
17
|
+
`#!/bin/sh
|
|
18
|
+
printf '%s\\n' "$*" >> "$HEYGEN_CAPTURE_PATH"
|
|
19
|
+
case "$*" in
|
|
20
|
+
*"voice list"*) printf '%s\\n' '{"data":[{"voice_id":"voice-123"}]}' ;;
|
|
21
|
+
*"voice speech create"*) printf '%s\\n' '{"data":{"audio_url":"https://example.com/voice.mp3","duration":1.5}}' ;;
|
|
22
|
+
esac
|
|
23
|
+
`,
|
|
24
|
+
);
|
|
25
|
+
chmodSync(heygenPath, 0o755);
|
|
26
|
+
process.env.PATH = `${dir}:${previousPath ?? ""}`;
|
|
27
|
+
process.env.HEYGEN_CAPTURE_PATH = capturePath;
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const result = await heygenTtsGenerate("Hello from media-use", {});
|
|
31
|
+
const invocations = readFileSync(capturePath, "utf8").trim().split("\n");
|
|
32
|
+
|
|
33
|
+
assert.equal(invocations.length, 2);
|
|
34
|
+
assert.match(invocations[0], /^voice list /);
|
|
35
|
+
assert.doesNotMatch(invocations[0], /X-HeyGen-Client-Source/);
|
|
36
|
+
assert.match(invocations[1], /voice speech create/);
|
|
37
|
+
assert.match(invocations[1], /X-HeyGen-Client-Source: media-use/);
|
|
38
|
+
assert.equal(result?.url, "https://example.com/voice.mp3");
|
|
39
|
+
} finally {
|
|
40
|
+
if (previousPath === undefined) delete process.env.PATH;
|
|
41
|
+
else process.env.PATH = previousPath;
|
|
42
|
+
if (previousCapturePath === undefined) delete process.env.HEYGEN_CAPTURE_PATH;
|
|
43
|
+
else process.env.HEYGEN_CAPTURE_PATH = previousCapturePath;
|
|
44
|
+
rmSync(dir, { recursive: true, force: true });
|
|
45
|
+
}
|
|
46
|
+
});
|