hyperframes 0.8.87 → 0.8.89
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/dist/{add-U6IYWNEI.js → add-JDX7VHMM.js} +5 -5
- package/dist/{asset-IVVTJRJ3.js → asset-ZQU3ZAHZ.js} +2 -2
- package/dist/{auth-B52RVHDK.js → auth-ZI5X6SXG.js} +1 -1
- package/dist/{autoUpdate-JE4IONTI.js → autoUpdate-BDREI4XN.js} +2 -2
- package/dist/{backgroundChecks-CGYRZGOR.js → backgroundChecks-52MY7SIC.js} +3 -3
- package/dist/backgroundChecksWorker.js +3 -3
- package/dist/{beats-E5V4HEFQ.js → beats-77Z6UPMB.js} +4 -4
- package/dist/{benchmark-5OYJMUXY.js → benchmark-HR242B6Q.js} +5 -5
- package/dist/{browser-ZCYC2U3H.js → browser-CLQNZB5J.js} +3 -3
- package/dist/{catalog-Q4L4P7VV.js → catalog-OAJVYTXP.js} +5 -5
- package/dist/{check-HM46YAG7.js → check-AJB25CBI.js} +9 -9
- package/dist/{checkBrowser-GMHXULQC.js → checkBrowser-RTZFBO62.js} +7 -7
- package/dist/{chunk-UUMXWC2H.js → chunk-BEZ5LKUM.js} +2 -2
- package/dist/{chunk-BQ4N7ECC.js → chunk-C22UJWNH.js} +1 -1
- package/dist/{chunk-NETMPMSJ.js → chunk-CZXT2DRB.js} +2 -2
- package/dist/{chunk-V3RCZR7L.js → chunk-EAJNP7C7.js} +1 -1
- package/dist/{chunk-VP53XFPD.js → chunk-ELQAAO4F.js} +1 -1
- package/dist/{chunk-KGEVE5JJ.js → chunk-J4ZDN5N7.js} +2 -2
- package/dist/{chunk-GMEL3UDG.js → chunk-LTCGOUKZ.js} +2 -2
- package/dist/{chunk-4MLISXWH.js → chunk-PKK6QWAQ.js} +1 -1
- package/dist/{chunk-C4LRCQ7O.js → chunk-RLKPZQKI.js} +2 -2
- package/dist/{chunk-V5643PWM.js → chunk-ROFOQAPQ.js} +2 -2
- package/dist/{chunk-O24S2D45.js → chunk-S2F6QM6J.js} +1 -1
- package/dist/{chunk-RXHJRTDQ.js → chunk-SAYNFMCN.js} +1 -1
- package/dist/{chunk-4GPB7TYV.js → chunk-TQNLHVZB.js} +2 -2
- package/dist/{chunk-S4NITUIE.js → chunk-XMDQQAIB.js} +1 -1
- package/dist/{chunk-OAT3NROY.js → chunk-Z4LBLZ3I.js} +2 -2
- package/dist/{clean-LG7EU4Z6.js → clean-YNSDX62K.js} +3 -3
- package/dist/cli.js +44 -44
- package/dist/{cloud-DQSKLPJH.js → cloud-O6C6D2CC.js} +4 -4
- package/dist/{compare-QOEPDS3E.js → compare-32ZYX7EU.js} +4 -4
- package/dist/{component-ZVGX6SCH.js → component-OX6ODG5G.js} +3 -3
- package/dist/{compositions-JVJO7CKJ.js → compositions-IAWH2EMO.js} +5 -5
- package/dist/{delete-573HEO7D.js → delete-4AGDWC2T.js} +2 -2
- package/dist/{doctor-MB6A7TN7.js → doctor-AH7IX5Z6.js} +3 -3
- package/dist/{events-Z6DV7LT7.js → events-6M6FDQZZ.js} +3 -3
- package/dist/{events-7F6C7O2R.js → events-CJRCDPWV.js} +2 -2
- package/dist/{feedback-ELSUXCWM.js → feedback-C6DJ6XKA.js} +4 -4
- package/dist/{figma-4NL4EZCF.js → figma-L5RHNGY7.js} +3 -3
- package/dist/fontLocalizeCli.js +1 -1
- package/dist/{get-4W2IN7H3.js → get-TBBCYF5B.js} +2 -2
- package/dist/{grade-compare-744LENMN.js → grade-compare-FNPPD6UE.js} +4 -4
- package/dist/{help-VET2A2JX.js → help-L4V4PV7C.js} +1 -1
- package/dist/{history-BWJH4567.js → history-EEWE52BC.js} +5 -5
- package/dist/hyperframes-player.global.js +1 -1
- package/dist/{info-TZZU5K44.js → info-VQYR7VW6.js} +5 -5
- package/dist/{init-5QPBGFAP.js → init-L5BK5HGZ.js} +6 -6
- package/dist/{inspect-ZKM46DT2.js → inspect-4DSMPUHN.js} +6 -6
- package/dist/{keyframes-L4PTFVRB.js → keyframes-AXHMLOSU.js} +5 -5
- package/dist/{layout-RCQKNYDP.js → layout-ZV23GQU6.js} +6 -6
- package/dist/{lint-FDUB6APN.js → lint-HZIRYUSA.js} +6 -6
- package/dist/{list-D6NIQOEY.js → list-DLBDHWZ2.js} +2 -2
- package/dist/{login-M6AWHM6U.js → login-3PLQRQEP.js} +4 -4
- package/dist/{media-treatment-MRNN2DOS.js → media-treatment-7TZ7L7NU.js} +5 -5
- package/dist/{normalize-audio-WB76C7OQ.js → normalize-audio-SX64JNLW.js} +4 -4
- package/dist/{play-N5FZSNGF.js → play-VX2LSHXR.js} +4 -4
- package/dist/{present-N24VMIWP.js → present-F2DPEKZU.js} +4 -4
- package/dist/{preview-RO3INI3V.js → preview-ARITZNNL.js} +5 -5
- package/dist/{render-FQ7VV2TU.js → render-2VWTXUCT.js} +5 -5
- package/dist/{render-FULEBKUN.js → render-FT4NTFKZ.js} +5 -5
- package/dist/skills/media-use/audio/references/bgm.md +2 -0
- package/dist/skills/media-use/audio/references/sfx.md +2 -3
- package/dist/skills/media-use/audio/references/tts.md +1 -1
- package/dist/skills/media-use/audio/scripts/audio.mjs +5 -2
- package/dist/skills/media-use/audio/scripts/audio.test.mjs +61 -8
- package/dist/skills/media-use/audio/scripts/gemini-pipeline.test.mjs +14 -3
- package/dist/skills/media-use/audio/scripts/lib/bgm.mjs +8 -4
- package/dist/skills/media-use/audio/scripts/lib/bgm.test.mjs +45 -1
- package/dist/skills/media-use/audio/scripts/lib/media-record.mjs +43 -2
- package/dist/skills/media-use/audio/scripts/lib/media-record.test.mjs +54 -2
- package/dist/skills/media-use/audio/scripts/lib/sfx.mjs +36 -17
- package/dist/skills/media-use/audio/scripts/lib/sfx.test.mjs +53 -1
- package/dist/skills/media-use/scripts/lib/manifest.mjs +3 -0
- package/dist/skills/media-use/scripts/resolve.mjs +4 -4
- package/dist/{skills-JPRPPT3N.js → skills-KDD7E5CB.js} +5 -5
- package/dist/{skillsUpdateCheck-PPLBLSII.js → skillsUpdateCheck-DXGEK4AH.js} +3 -3
- package/dist/{snapshot-HBS2KVJV.js → snapshot-BSOD5KKR.js} +4 -4
- package/dist/studio/assets/{hyperframes-player-CtNVImMi.js → hyperframes-player-CVoS7kv8.js} +1 -1
- package/dist/studio/assets/{index-DZKcDAtf.js → index-BM8PwYby.js} +187 -187
- package/dist/studio/assets/{index-DJ-Ol7YS.js → index-D35-L_yn.js} +1 -1
- package/dist/studio/assets/{index-B_MF86RE.js → index-DBub9qQY.js} +1 -1
- package/dist/studio/{chunk-7PLTC6SV.js → chunk-KU4F7QZ3.js} +9 -1
- package/dist/studio/chunk-KU4F7QZ3.js.map +1 -0
- package/dist/studio/index.d.ts +42 -5
- package/dist/studio/index.html +1 -1
- package/dist/studio/index.js +269 -94
- package/dist/studio/index.js.map +1 -1
- package/dist/studio/player.js +1 -1
- package/dist/{studioServer-HIKBLJCQ.js → studioServer-2IK5LLRR.js} +4 -4
- package/dist/{telemetry-ZJ6VMRWN.js → telemetry-6P5WHWYI.js} +3 -3
- package/dist/{timeline-NMCFP4IO.js → timeline-QZ4Q2LED.js} +5 -5
- package/dist/{tokens-ILHCMG2C.js → tokens-VXZYKHFV.js} +2 -2
- package/dist/{transcribe-TC7XR5IS.js → transcribe-RQ6RHU2V.js} +3 -3
- package/dist/{updateCheck-D7C3KAJT.js → updateCheck-RRWEIZY7.js} +2 -2
- package/dist/{upgrade-ZGAFWJLS.js → upgrade-LI5TVSQQ.js} +2 -2
- package/dist/{validate-T243OVX2.js → validate-I56RAOC6.js} +6 -6
- package/package.json +1 -1
- package/dist/studio/chunk-7PLTC6SV.js.map +0 -1
|
@@ -12,9 +12,10 @@
|
|
|
12
12
|
// Missing/failed BGM never blocks a render.
|
|
13
13
|
|
|
14
14
|
import { spawn, spawnSync } from "node:child_process";
|
|
15
|
-
import { existsSync, mkdirSync, openSync, closeSync } from "node:fs";
|
|
15
|
+
import { existsSync, mkdirSync, openSync, closeSync, rmSync } from "node:fs";
|
|
16
16
|
import { join } from "node:path";
|
|
17
17
|
import { downloadTo, searchSounds } from "./heygen.mjs";
|
|
18
|
+
import { agentWritePath } from "./media-record.mjs";
|
|
18
19
|
import { pythonInvocation } from "./python.mjs";
|
|
19
20
|
|
|
20
21
|
const r3 = (x) => Number(x.toFixed(3));
|
|
@@ -45,12 +46,12 @@ function pipInstall(deps) {
|
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
// ── retrieval (HeyGen music library) ──────────────────────────────────────────
|
|
48
|
-
export async function retrieveBgm({ query, headers, hyperframesDir, hasVoice }) {
|
|
49
|
+
export async function retrieveBgm({ query, headers, hyperframesDir, hasVoice, anomalies }) {
|
|
49
50
|
const q = query || "calm cinematic underscore";
|
|
50
51
|
const results = await searchSounds(q, "music", headers, { limit: 5 });
|
|
51
52
|
if (!results.length) return null;
|
|
52
53
|
const top = results[0];
|
|
53
|
-
const rel = "assets/bgm/track.mp3";
|
|
54
|
+
const rel = agentWritePath(hyperframesDir, "assets/bgm/track.mp3", { anomalies });
|
|
54
55
|
await downloadTo(top.audio_url, join(hyperframesDir, rel));
|
|
55
56
|
return {
|
|
56
57
|
path: rel,
|
|
@@ -113,8 +114,9 @@ export function generateBgmDetached({
|
|
|
113
114
|
lyriaRecipe,
|
|
114
115
|
seedSeconds = 28,
|
|
115
116
|
hasVoice,
|
|
117
|
+
anomalies,
|
|
116
118
|
}) {
|
|
117
|
-
const rel = "assets/bgm/track.wav";
|
|
119
|
+
const rel = agentWritePath(hyperframesDir, "assets/bgm/track.wav", { anomalies });
|
|
118
120
|
const abs = join(hyperframesDir, rel);
|
|
119
121
|
mkdirSync(join(hyperframesDir, "assets", "bgm"), { recursive: true });
|
|
120
122
|
const log = join(hyperframesDir, "assets", "bgm", `bgm-${Date.now()}.log`);
|
|
@@ -141,6 +143,7 @@ export function generateBgmDetached({
|
|
|
141
143
|
"--prompt",
|
|
142
144
|
prompt,
|
|
143
145
|
]);
|
|
146
|
+
rmSync(abs, { force: true }); // wait-bgm takes any file here as the finished track
|
|
144
147
|
const proc = spawn(cmd, args, { detached: true, stdio: ["ignore", fd, fd] });
|
|
145
148
|
proc.unref();
|
|
146
149
|
closeSync(fd);
|
|
@@ -159,6 +162,7 @@ export function generateBgmDetached({
|
|
|
159
162
|
const loops = targetS > seedS ? Math.ceil(targetS / seedS) : 1;
|
|
160
163
|
const script = musicgenScript({ prompt, abs, targetS, seedS });
|
|
161
164
|
const { cmd, args } = pythonInvocation(["-c", script]);
|
|
165
|
+
rmSync(abs, { force: true });
|
|
162
166
|
const proc = spawn(cmd, args, { detached: true, stdio: ["ignore", fd, fd] });
|
|
163
167
|
proc.unref();
|
|
164
168
|
closeSync(fd);
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import { test } from "node:test";
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
|
-
import {
|
|
3
|
+
import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { appendRecord } from "../../../scripts/lib/manifest.mjs";
|
|
7
|
+
import {
|
|
8
|
+
BGM_BED_VOLUME,
|
|
9
|
+
BGM_SILENT_VOLUME,
|
|
10
|
+
bgmDefaultVolume,
|
|
11
|
+
generateBgmDetached,
|
|
12
|
+
} from "./bgm.mjs";
|
|
4
13
|
|
|
5
14
|
// Regression: narrated pipelines used to ship BGM at 0.8 (≈ -2 dB), ~16 dB
|
|
6
15
|
// hotter than a music bed under a voice should be. The default under narration
|
|
@@ -28,3 +37,38 @@ test("the narrated default is well below the voice (≈ 0 dBFS)", () => {
|
|
|
28
37
|
`bed should sit ≥16 dB under the voice, got ${separation.toFixed(1)} dB`,
|
|
29
38
|
);
|
|
30
39
|
});
|
|
40
|
+
|
|
41
|
+
test(
|
|
42
|
+
"generating music again clears the engine's old track, so waiting cannot mistake it for the new one",
|
|
43
|
+
{ skip: process.platform === "win32" && "the fake python is a shell script" },
|
|
44
|
+
(t) => {
|
|
45
|
+
const dir = mkdtempSync(join(tmpdir(), "hf-bgm-"));
|
|
46
|
+
const path = process.env.PATH;
|
|
47
|
+
t.after(() => {
|
|
48
|
+
process.env.PATH = path;
|
|
49
|
+
rmSync(dir, { recursive: true, force: true });
|
|
50
|
+
});
|
|
51
|
+
mkdirSync(join(dir, "bin"));
|
|
52
|
+
for (const name of ["python3", "python"])
|
|
53
|
+
writeFileSync(join(dir, "bin", name), "#!/bin/sh\nexit 0\n", { mode: 0o755 });
|
|
54
|
+
process.env.PATH = `${join(dir, "bin")}:${path}`;
|
|
55
|
+
mkdirSync(join(dir, "assets/bgm"), { recursive: true });
|
|
56
|
+
writeFileSync(join(dir, "assets/bgm/track.wav"), "last run's track");
|
|
57
|
+
appendRecord(dir, {
|
|
58
|
+
id: "bgm_001",
|
|
59
|
+
type: "bgm",
|
|
60
|
+
path: "assets/bgm/track.wav",
|
|
61
|
+
source: "generated",
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const gen = generateBgmDetached({
|
|
65
|
+
prompt: "calm",
|
|
66
|
+
durationS: 5,
|
|
67
|
+
hyperframesDir: dir,
|
|
68
|
+
anomalies: [],
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
assert.equal(gen.path, "assets/bgm/track.wav");
|
|
72
|
+
assert.equal(existsSync(join(dir, "assets/bgm/track.wav")), false);
|
|
73
|
+
},
|
|
74
|
+
);
|
|
@@ -1,6 +1,48 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { extname, join } from "node:path";
|
|
3
|
+
import { AGENT_SOURCES, latestRecordFor, recordInPlace } from "../../../scripts/lib/manifest.mjs";
|
|
2
4
|
import { regenerateIndex } from "../../../scripts/lib/index-gen.mjs";
|
|
3
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Where the engine may write `rel`: there, unless another file of this run has `taken` it or the file there is
|
|
8
|
+
* the person's (not `reusable` and not recorded as agent-made). Then the first free `name-2.ext`, with an anomaly.
|
|
9
|
+
*/
|
|
10
|
+
export function agentWritePath(
|
|
11
|
+
hyperframesDir,
|
|
12
|
+
rel,
|
|
13
|
+
{ anomalies, taken = new Set(), reusable = () => false },
|
|
14
|
+
) {
|
|
15
|
+
const free = (path) =>
|
|
16
|
+
!taken.has(path) &&
|
|
17
|
+
(!existsSync(join(hyperframesDir, path)) ||
|
|
18
|
+
reusable(path) ||
|
|
19
|
+
AGENT_SOURCES.includes(latestRecordFor(hyperframesDir, path)?.source));
|
|
20
|
+
if (free(rel)) return rel;
|
|
21
|
+
const ext = extname(rel);
|
|
22
|
+
const stem = rel.slice(0, rel.length - ext.length);
|
|
23
|
+
let n = 2;
|
|
24
|
+
while (!free(`${stem}-${n}${ext}`)) n++;
|
|
25
|
+
const path = `${stem}-${n}${ext}`;
|
|
26
|
+
const why = taken.has(rel)
|
|
27
|
+
? "another file of this run goes there"
|
|
28
|
+
: "the file there is yours (the media manifest does not record it as made by the engine)";
|
|
29
|
+
const note = `${rel}: kept, because ${why}; writing ${path} instead (audio_meta.json has the path used)`;
|
|
30
|
+
if (!anomalies.includes(note)) anomalies.push(note);
|
|
31
|
+
return path;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Each spoken line's file, picked before lines synthesize concurrently so two never land on one free name. */
|
|
35
|
+
export function voicePaths(hyperframesDir, lines, anomalies) {
|
|
36
|
+
const taken = new Set();
|
|
37
|
+
const paths = new Map();
|
|
38
|
+
for (const line of lines.filter((l) => String(l.text ?? "").trim())) {
|
|
39
|
+
const rel = agentWritePath(hyperframesDir, `assets/voice/${line.id}.wav`, { anomalies, taken });
|
|
40
|
+
taken.add(rel);
|
|
41
|
+
paths.set(String(line.id), rel);
|
|
42
|
+
}
|
|
43
|
+
return paths;
|
|
44
|
+
}
|
|
45
|
+
|
|
4
46
|
const SFX_SOURCES = { heygen: "search", local: "bundled" };
|
|
5
47
|
|
|
6
48
|
// The files one engine run wrote, each with how it was made, for the project's media manifest.
|
|
@@ -32,7 +74,6 @@ export function writtenAssets({ only, lines, voices, ttsProvider, bgm, bgmFields
|
|
|
32
74
|
if (only.has("sfx")) {
|
|
33
75
|
for (const cue of new Map(sfx.map((entry) => [entry.file, entry])).values()) {
|
|
34
76
|
const source = SFX_SOURCES[cue.source];
|
|
35
|
-
if (!source) continue;
|
|
36
77
|
const provider = source === "search" ? "heygen" : "bundled.sfx";
|
|
37
78
|
assets.push({
|
|
38
79
|
path: cue.file,
|
|
@@ -4,7 +4,7 @@ import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "nod
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
6
|
import { findByPrompt, readManifest } from "../../../scripts/lib/manifest.mjs";
|
|
7
|
-
import { recordInManifest, writtenAssets } from "./media-record.mjs";
|
|
7
|
+
import { agentWritePath, recordInManifest, voicePaths, writtenAssets } from "./media-record.mjs";
|
|
8
8
|
|
|
9
9
|
const noBgm = { bgm: null, bgmFields: { bgm_pending: false } };
|
|
10
10
|
|
|
@@ -98,7 +98,6 @@ test("music and sound effects are marked by where they came from", () => {
|
|
|
98
98
|
{ file: "assets/sfx/whoosh.mp3", name: "whoosh", source: "local" },
|
|
99
99
|
{ file: "assets/sfx/whoosh.mp3", name: "whoosh", source: "local" },
|
|
100
100
|
{ file: "assets/sfx/glass.mp3", name: "glass", source: "heygen" },
|
|
101
|
-
{ file: "assets/sfx/pop.mp3", name: "pop", source: "project" },
|
|
102
101
|
],
|
|
103
102
|
});
|
|
104
103
|
|
|
@@ -139,6 +138,59 @@ test("a file that cannot be recorded becomes an anomaly, not a failure", (t) =>
|
|
|
139
138
|
assert.match(anomalies[0], /^assets\/voice\/01\.wav: not recorded in the media manifest/);
|
|
140
139
|
});
|
|
141
140
|
|
|
141
|
+
test("the engine writes over only its own files, else the next free name", (t) => {
|
|
142
|
+
const dir = project(t);
|
|
143
|
+
mkdirSync(join(dir, "assets/sfx"), { recursive: true });
|
|
144
|
+
const names = ["mine", "made", "adopted", "twice", "twice-2", "kept", "kept-2"];
|
|
145
|
+
for (const name of names) writeFileSync(join(dir, `assets/sfx/${name}.mp3`), name);
|
|
146
|
+
for (const path of ["assets/sfx/made.mp3", "assets/sfx/kept-2.mp3"])
|
|
147
|
+
recordInManifest(dir, [{ path, type: "sfx", source: "search" }]);
|
|
148
|
+
recordInManifest(dir, [{ path: "assets/sfx/adopted.mp3", type: "sfx", source: "existing" }]);
|
|
149
|
+
const anomalies = [];
|
|
150
|
+
const at = (name, reusable) =>
|
|
151
|
+
agentWritePath(dir, `assets/sfx/${name}.mp3`, { anomalies, reusable });
|
|
152
|
+
|
|
153
|
+
assert.equal(at("new"), "assets/sfx/new.mp3");
|
|
154
|
+
assert.equal(at("made"), "assets/sfx/made.mp3");
|
|
155
|
+
assert.equal(
|
|
156
|
+
at("mine", (path) => path === "assets/sfx/mine.mp3"),
|
|
157
|
+
"assets/sfx/mine.mp3",
|
|
158
|
+
);
|
|
159
|
+
assert.deepEqual(anomalies, []);
|
|
160
|
+
assert.equal(at("mine"), "assets/sfx/mine-2.mp3");
|
|
161
|
+
assert.equal(at("adopted"), "assets/sfx/adopted-2.mp3");
|
|
162
|
+
assert.equal(at("twice"), "assets/sfx/twice-3.mp3");
|
|
163
|
+
assert.equal(at("kept"), "assets/sfx/kept-2.mp3");
|
|
164
|
+
assert.equal(anomalies.length, 4);
|
|
165
|
+
assert.match(
|
|
166
|
+
anomalies[0],
|
|
167
|
+
/^assets\/sfx\/mine\.mp3: kept, .* writing assets\/sfx\/mine-2\.mp3 instead/,
|
|
168
|
+
);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test("two spoken lines never share a file when one's name is taken by the person", (t) => {
|
|
172
|
+
const dir = project(t);
|
|
173
|
+
mkdirSync(join(dir, "assets/voice"), { recursive: true });
|
|
174
|
+
writeFileSync(join(dir, "assets/voice/hook.wav"), "the person's own hook");
|
|
175
|
+
const anomalies = [];
|
|
176
|
+
|
|
177
|
+
const paths = voicePaths(
|
|
178
|
+
dir,
|
|
179
|
+
[
|
|
180
|
+
{ id: "hook", text: "First" },
|
|
181
|
+
{ id: "hook-2", text: "Second" },
|
|
182
|
+
{ id: "blank", text: " " },
|
|
183
|
+
],
|
|
184
|
+
anomalies,
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
assert.deepEqual(Object.fromEntries(paths), {
|
|
188
|
+
hook: "assets/voice/hook-2.wav",
|
|
189
|
+
"hook-2": "assets/voice/hook-2-2.wav",
|
|
190
|
+
});
|
|
191
|
+
assert.equal(anomalies.length, 2);
|
|
192
|
+
});
|
|
193
|
+
|
|
142
194
|
test("a record never takes the id of a download still in flight", (t) => {
|
|
143
195
|
const dir = project(t);
|
|
144
196
|
mkdirSync(join(dir, ".media/audio/bgm"), { recursive: true });
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
import { copyFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
16
16
|
import { join } from "node:path";
|
|
17
17
|
import { downloadTo, searchSounds } from "./heygen.mjs";
|
|
18
|
+
import { latestRecordFor } from "../../../scripts/lib/manifest.mjs";
|
|
19
|
+
import { agentWritePath } from "./media-record.mjs";
|
|
18
20
|
|
|
19
21
|
const SFX_VOLUME = 0.35;
|
|
20
22
|
const slug = (s) =>
|
|
@@ -31,6 +33,8 @@ export async function resolveSfx({ cues, heygenOK, headers, hyperframesDir, sfxL
|
|
|
31
33
|
const sfx = [];
|
|
32
34
|
const anomalies = [];
|
|
33
35
|
const destDir = join(hyperframesDir, "assets", "sfx");
|
|
36
|
+
// Each effect's file this run: one effect named twice gets one file, and two effects never share one.
|
|
37
|
+
const fileFor = new Map();
|
|
34
38
|
|
|
35
39
|
// Dedupe identical (id,name) cues — the same effect named twice in one line
|
|
36
40
|
// downloads/copies once.
|
|
@@ -57,8 +61,14 @@ export async function resolveSfx({ cues, heygenOK, headers, hyperframesDir, sfxL
|
|
|
57
61
|
continue;
|
|
58
62
|
}
|
|
59
63
|
const top = results[0];
|
|
60
|
-
const file =
|
|
64
|
+
const file =
|
|
65
|
+
fileFor.get(slug(name)) ??
|
|
66
|
+
agentWritePath(hyperframesDir, `assets/sfx/${slug(name)}.mp3`, {
|
|
67
|
+
anomalies,
|
|
68
|
+
taken: new Set(fileFor.values()),
|
|
69
|
+
});
|
|
61
70
|
await downloadTo(top.audio_url, join(hyperframesDir, file));
|
|
71
|
+
fileFor.set(slug(name), file);
|
|
62
72
|
sfx.push({
|
|
63
73
|
id,
|
|
64
74
|
name,
|
|
@@ -111,32 +121,41 @@ export async function resolveSfx({ cues, heygenOK, headers, hyperframesDir, sfxL
|
|
|
111
121
|
continue;
|
|
112
122
|
}
|
|
113
123
|
const src = join(sfxLibDir, hit.file);
|
|
114
|
-
const
|
|
124
|
+
const library = existsSync(src) ? readFileSync(src) : null;
|
|
125
|
+
const isLibraryCopy = (rel) =>
|
|
126
|
+
library &&
|
|
127
|
+
existsSync(join(hyperframesDir, rel)) &&
|
|
128
|
+
readFileSync(join(hyperframesDir, rel)).equals(library);
|
|
129
|
+
const destRel =
|
|
130
|
+
fileFor.get(hit.file) ??
|
|
131
|
+
agentWritePath(hyperframesDir, `assets/sfx/${hit.file}`, {
|
|
132
|
+
anomalies,
|
|
133
|
+
taken: new Set(fileFor.values()),
|
|
134
|
+
// An unrecorded copy of the library file is one an engine run made before the manifest had it.
|
|
135
|
+
reusable: (rel) => !latestRecordFor(hyperframesDir, rel) && isLibraryCopy(rel),
|
|
136
|
+
});
|
|
115
137
|
const dest = join(hyperframesDir, destRel);
|
|
116
138
|
// The bundled library may be incomplete: some installs of the skill ship
|
|
117
139
|
// manifest.json without the actual mp3s. Pushing an sfx entry that points at
|
|
118
140
|
// a file we never copied produces a dangling reference that silently drops
|
|
119
141
|
// downstream ("not on disk"). Surface it as a loud anomaly and skip the cue
|
|
120
142
|
// instead, so the audio_meta never references a missing file.
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
133
|
-
// A file already there that is not the library's copy is the person's own.
|
|
134
|
-
const own = reused && !(existsSync(src) && readFileSync(dest).equals(readFileSync(src)));
|
|
143
|
+
if (library) {
|
|
144
|
+
if (!isLibraryCopy(destRel)) copyFileSync(src, dest);
|
|
145
|
+
} else if (!existsSync(dest)) {
|
|
146
|
+
anomalies.push(
|
|
147
|
+
`sfx "${name}" (id ${id}): bundled file ${hit.file} missing from the offline ` +
|
|
148
|
+
`library (${sfxLibDir}) — skipped. Reinstall the media-use skill to ` +
|
|
149
|
+
`restore assets/sfx/*.mp3, or configure a HeyGen credential for retrieval.`,
|
|
150
|
+
);
|
|
151
|
+
continue;
|
|
152
|
+
} // else the engine's earlier copy at dest stands in for the file this install lacks
|
|
153
|
+
fileFor.set(hit.file, destRel);
|
|
135
154
|
sfx.push({
|
|
136
155
|
id,
|
|
137
156
|
name,
|
|
138
157
|
file: destRel,
|
|
139
|
-
source:
|
|
158
|
+
source: "local",
|
|
140
159
|
offset_s: 0,
|
|
141
160
|
duration_s: r3(hit.duration),
|
|
142
161
|
volume: SFX_VOLUME,
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { test } from "node:test";
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
|
-
import { mkdtempSync, mkdirSync, writeFileSync, existsSync, rmSync } from "node:fs";
|
|
3
|
+
import { mkdtempSync, mkdirSync, writeFileSync, existsSync, readFileSync, rmSync } from "node:fs";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
|
+
import { appendRecord } from "../../../scripts/lib/manifest.mjs";
|
|
6
7
|
import { resolveSfx } from "./sfx.mjs";
|
|
7
8
|
|
|
8
9
|
// Offline (no HeyGen) SFX resolution: the bundled library may ship manifest.json
|
|
@@ -67,3 +68,54 @@ test("offline: a matched-but-missing bundled file yields an anomaly and NO dangl
|
|
|
67
68
|
assert.ok(!existsSync(join(projDir, "assets/sfx/whoosh.mp3")), "nothing copied");
|
|
68
69
|
});
|
|
69
70
|
});
|
|
71
|
+
|
|
72
|
+
const whooshLibrary = (libDir, withFile) => {
|
|
73
|
+
writeFileSync(
|
|
74
|
+
join(libDir, "manifest.json"),
|
|
75
|
+
JSON.stringify({ whoosh: { file: "whoosh.mp3", duration: 0.8 } }),
|
|
76
|
+
);
|
|
77
|
+
if (withFile) writeFileSync(join(libDir, "whoosh.mp3"), "ID3-library-bytes");
|
|
78
|
+
};
|
|
79
|
+
const record = (projDir, source) =>
|
|
80
|
+
appendRecord(projDir, { id: "sfx_001", type: "sfx", path: "assets/sfx/whoosh.mp3", source });
|
|
81
|
+
const offline = (libDir, projDir) =>
|
|
82
|
+
resolveSfx({
|
|
83
|
+
cues: [{ id: "s1", name: "whoosh" }],
|
|
84
|
+
heygenOK: false,
|
|
85
|
+
hyperframesDir: projDir,
|
|
86
|
+
sfxLibDir: libDir,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("offline: the engine's earlier copy stands in for a library file this install lacks", async () => {
|
|
90
|
+
await withDirs(async ({ libDir, projDir }) => {
|
|
91
|
+
whooshLibrary(libDir, false);
|
|
92
|
+
mkdirSync(join(projDir, "assets/sfx"), { recursive: true });
|
|
93
|
+
writeFileSync(join(projDir, "assets/sfx/whoosh.mp3"), "engine copy");
|
|
94
|
+
record(projDir, "bundled");
|
|
95
|
+
|
|
96
|
+
const { sfx, anomalies } = await offline(libDir, projDir);
|
|
97
|
+
|
|
98
|
+
assert.deepEqual(
|
|
99
|
+
sfx.map(({ file }) => file),
|
|
100
|
+
["assets/sfx/whoosh.mp3"],
|
|
101
|
+
);
|
|
102
|
+
assert.deepEqual(anomalies, []);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("offline: a person's file with the library's bytes stays theirs once recorded as their own", async () => {
|
|
107
|
+
await withDirs(async ({ libDir, projDir }) => {
|
|
108
|
+
whooshLibrary(libDir, true);
|
|
109
|
+
mkdirSync(join(projDir, "assets/sfx"), { recursive: true });
|
|
110
|
+
writeFileSync(join(projDir, "assets/sfx/whoosh.mp3"), "ID3-library-bytes");
|
|
111
|
+
record(projDir, "existing");
|
|
112
|
+
|
|
113
|
+
const { sfx } = await offline(libDir, projDir);
|
|
114
|
+
|
|
115
|
+
assert.deepEqual(
|
|
116
|
+
sfx.map(({ file }) => file),
|
|
117
|
+
["assets/sfx/whoosh-2.mp3"],
|
|
118
|
+
);
|
|
119
|
+
assert.equal(readFileSync(join(projDir, "assets/sfx/whoosh.mp3"), "utf8"), "ID3-library-bytes");
|
|
120
|
+
});
|
|
121
|
+
});
|
|
@@ -79,6 +79,9 @@ export function appendRecord(projectDir, record) {
|
|
|
79
79
|
appendFileSync(p, line);
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
/** Sources that mean the agent made or fetched the file; any other file is the person's own. */
|
|
83
|
+
export const AGENT_SOURCES = ["generated", "search", "bundled"];
|
|
84
|
+
|
|
82
85
|
/** The record a path has now: the manifest only appends, so the last one for a path wins. */
|
|
83
86
|
export function latestRecordFor(projectDir, path) {
|
|
84
87
|
return readManifest(projectDir).findLast((record) => record.path === path);
|
|
@@ -5,6 +5,7 @@ import { existsSync, statSync, writeFileSync, renameSync, rmSync, realpathSync }
|
|
|
5
5
|
import { resolve, join, extname, basename, relative, isAbsolute, sep } from "node:path";
|
|
6
6
|
import { parseArgs } from "node:util";
|
|
7
7
|
import {
|
|
8
|
+
AGENT_SOURCES,
|
|
8
9
|
appendRecord,
|
|
9
10
|
latestRecordFor,
|
|
10
11
|
recordInPlace,
|
|
@@ -60,7 +61,6 @@ import {
|
|
|
60
61
|
} from "./lib/local-media-search.mjs";
|
|
61
62
|
|
|
62
63
|
const INGEST_TYPES = listTypes();
|
|
63
|
-
const RECORDED_SOURCES = ["generated", "search", "bundled"];
|
|
64
64
|
const DEFAULT_EXT = {
|
|
65
65
|
bgm: ".wav",
|
|
66
66
|
sfx: ".mp3",
|
|
@@ -130,7 +130,7 @@ Options:
|
|
|
130
130
|
--reuse <sha> Import a specific global-cache asset (by content sha/prefix,
|
|
131
131
|
from --candidates) into this project
|
|
132
132
|
--from <file> Freeze a local file or direct public URL (ingest)
|
|
133
|
-
--source <how> With --from: how the file was made (${
|
|
133
|
+
--source <how> With --from: how the file was made (${AGENT_SOURCES.join(" | ")}).
|
|
134
134
|
A file already inside the project is then recorded where it is
|
|
135
135
|
--params <json> Build an explicit parametric LUT (lut/grade only)
|
|
136
136
|
--for <media> Analyze a local image/video and add measured grade adjust
|
|
@@ -918,8 +918,8 @@ async function ingest(src) {
|
|
|
918
918
|
console.error(`error: refusing to ingest a 0-byte file: ${src}`);
|
|
919
919
|
process.exit(2);
|
|
920
920
|
}
|
|
921
|
-
if (args.source && !
|
|
922
|
-
console.error(`error: --source takes one of: ${
|
|
921
|
+
if (args.source && !AGENT_SOURCES.includes(args.source)) {
|
|
922
|
+
console.error(`error: --source takes one of: ${AGENT_SOURCES.join(", ")}`);
|
|
923
923
|
process.exit(2);
|
|
924
924
|
}
|
|
925
925
|
if (args.source && (type === "lut" || type === "grade")) {
|
|
@@ -9,8 +9,8 @@ import {
|
|
|
9
9
|
} from "./chunk-WMQNIQDJ.js";
|
|
10
10
|
import {
|
|
11
11
|
trackSkillsInstallSkipped
|
|
12
|
-
} from "./chunk-
|
|
13
|
-
import "./chunk-
|
|
12
|
+
} from "./chunk-S2F6QM6J.js";
|
|
13
|
+
import "./chunk-EAJNP7C7.js";
|
|
14
14
|
import {
|
|
15
15
|
diag
|
|
16
16
|
} from "./chunk-VWG7OQKU.js";
|
|
@@ -50,15 +50,15 @@ import {
|
|
|
50
50
|
isCoreSkill,
|
|
51
51
|
presentSkills,
|
|
52
52
|
pruneOrphanedLockEntries
|
|
53
|
-
} from "./chunk-
|
|
53
|
+
} from "./chunk-XMDQQAIB.js";
|
|
54
54
|
import {
|
|
55
55
|
withMeta
|
|
56
|
-
} from "./chunk-
|
|
56
|
+
} from "./chunk-SAYNFMCN.js";
|
|
57
57
|
import "./chunk-K4WIOZFP.js";
|
|
58
58
|
import "./chunk-XUXGIA7H.js";
|
|
59
59
|
import "./chunk-WERLJGIC.js";
|
|
60
60
|
import "./chunk-ISN5GRYI.js";
|
|
61
|
-
import "./chunk-
|
|
61
|
+
import "./chunk-Z4LBLZ3I.js";
|
|
62
62
|
import "./chunk-KNU5COPB.js";
|
|
63
63
|
import {
|
|
64
64
|
buildNpxCommand
|
|
@@ -9,13 +9,13 @@ import {
|
|
|
9
9
|
printSkillsUpdateNotice,
|
|
10
10
|
refreshSkillsCache,
|
|
11
11
|
skillsCheckDue
|
|
12
|
-
} from "./chunk-
|
|
13
|
-
import "./chunk-
|
|
12
|
+
} from "./chunk-XMDQQAIB.js";
|
|
13
|
+
import "./chunk-SAYNFMCN.js";
|
|
14
14
|
import "./chunk-K4WIOZFP.js";
|
|
15
15
|
import "./chunk-XUXGIA7H.js";
|
|
16
16
|
import "./chunk-WERLJGIC.js";
|
|
17
17
|
import "./chunk-ISN5GRYI.js";
|
|
18
|
-
import "./chunk-
|
|
18
|
+
import "./chunk-Z4LBLZ3I.js";
|
|
19
19
|
import "./chunk-KNU5COPB.js";
|
|
20
20
|
import "./chunk-M2WBOQ2Q.js";
|
|
21
21
|
export {
|
|
@@ -31,7 +31,7 @@ import "./chunk-HMQ27CEJ.js";
|
|
|
31
31
|
import "./chunk-JVYVKXTA.js";
|
|
32
32
|
import {
|
|
33
33
|
resolveProject
|
|
34
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-C22UJWNH.js";
|
|
35
35
|
import {
|
|
36
36
|
formatLintFindings
|
|
37
37
|
} from "./chunk-6CSYCFQV.js";
|
|
@@ -54,8 +54,8 @@ import {
|
|
|
54
54
|
getFFmpegInstallHint
|
|
55
55
|
} from "./chunk-TVCRUG2S.js";
|
|
56
56
|
import "./chunk-XMNWWURD.js";
|
|
57
|
-
import "./chunk-
|
|
58
|
-
import "./chunk-
|
|
57
|
+
import "./chunk-S2F6QM6J.js";
|
|
58
|
+
import "./chunk-EAJNP7C7.js";
|
|
59
59
|
import "./chunk-VWG7OQKU.js";
|
|
60
60
|
import {
|
|
61
61
|
c
|
|
@@ -103,7 +103,7 @@ import "./chunk-K4WIOZFP.js";
|
|
|
103
103
|
import "./chunk-XUXGIA7H.js";
|
|
104
104
|
import "./chunk-WERLJGIC.js";
|
|
105
105
|
import "./chunk-ISN5GRYI.js";
|
|
106
|
-
import "./chunk-
|
|
106
|
+
import "./chunk-Z4LBLZ3I.js";
|
|
107
107
|
import {
|
|
108
108
|
normalizeErrorMessage
|
|
109
109
|
} from "./chunk-KNU5COPB.js";
|
package/dist/studio/assets/{hyperframes-player-CtNVImMi.js → hyperframes-player-CVoS7kv8.js}
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var xe=Object.defineProperty;var ke=(r,i,e)=>i in r?xe(r,i,{enumerable:!0,configurable:!0,writable:!0,value:e}):r[i]=e;var h=(r,i,e)=>ke(r,typeof i!="symbol"?i+"":i,e);import{r as fe,i as Ie,a as w,b as _e,p as Oe,c as Ne,d as Fe,s as Ve,e as ge,F as ye}from"./index-
|
|
1
|
+
var xe=Object.defineProperty;var ke=(r,i,e)=>i in r?xe(r,i,{enumerable:!0,configurable:!0,writable:!0,value:e}):r[i]=e;var h=(r,i,e)=>ke(r,typeof i!="symbol"?i+"":i,e);import{r as fe,i as Ie,a as w,b as _e,p as Oe,c as Ne,d as Fe,s as Ve,e as ge,F as ye}from"./index-BM8PwYby.js";function He(r){return r.hasRuntime||r.runtimeInjected?!1:!!(r.hasNestedCompositions||r.hasTimelines&&r.attempts>=5)}function z(r){return typeof r=="object"&&r!==null}function Ue(r){return z(r)&&typeof r.getDuration=="function"}function We(r){return z(r)&&typeof r.duration=="function"&&typeof r.time=="function"&&typeof r.seek=="function"&&typeof r.play=="function"&&typeof r.pause=="function"}function ze(r){if(!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(r))throw new Error(`Invalid HyperFrames runtime version: ${r}`);return`https://cdn.jsdelivr.net/npm/@hyperframes/core@${r}/dist/hyperframe.runtime.iife.js`}const U=typeof __HYPERFRAMES_RUNTIME_CDN_URL__=="string"?__HYPERFRAMES_RUNTIME_CDN_URL__:ze("0.0.0-dev");function $(r){if(r===null)return null;const i=Number.parseInt(r,10);return Number.isFinite(i)&&i>0?i:null}function $e(r){const i=(r==null?void 0:r.querySelector("[data-composition-id][data-width][data-height]"))??(r==null?void 0:r.querySelector("[data-width][data-height]"));if(!i)return null;const e=$(i.getAttribute("data-width")),t=$(i.getAttribute("data-height"));return e!==null&&t!==null?{width:e,height:t}:null}class je{constructor(i,e){h(this,"_interval",null);h(this,"_runtimeInjected",!1);this._iframe=i,this._callbacks=e}get runtimeInjected(){return this._runtimeInjected}start(){this.stop(),this._runtimeInjected=!1;let i=0;this._interval=setInterval(()=>{var e;i++;try{const t=this._iframe.contentWindow;if(!t)return;const s=!!(t.__hf||t.__player),n=!!(t.__timelines&&Object.keys(t.__timelines).length>0),o=!!((e=this._iframe.contentDocument)!=null&&e.querySelector("[data-composition-src]"));if(He({hasRuntime:s,hasTimelines:n,hasNestedCompositions:o,runtimeInjected:this._runtimeInjected,attempts:i})){this._injectRuntime();return}if(this._runtimeInjected&&!s)return;const a=this._resolvePlaybackDurationAdapter(t);if(a&&a.getDuration()>0){this.stop();const c=$e(this._iframe.contentDocument);this._callbacks.onReady({duration:a.getDuration(),adapter:a,compositionSize:c});return}}catch{}i>=40&&(this.stop(),this._callbacks.onError("Composition timeline not found after 8s"))},200)}stop(){this._interval!==null&&(clearInterval(this._interval),this._interval=null)}resolveDirectTimelineAdapter(){try{const i=this._iframe.contentWindow;return i?this._resolveDirectTimelineAdapterFromWindow(i):null}catch{return null}}resolveDirectTimelineAdapterFromWindow(i){return this._resolveDirectTimelineAdapterFromWindow(i)}hasRuntimeBridge(i){return Reflect.get(i,"__hf")!==void 0||z(Reflect.get(i,"__player"))}_injectRuntime(){var i,e;this._runtimeInjected=!0;try{const t=this._iframe.contentDocument;if(!t)return;const s=t.createElement("script");s.src=U,(t.head||t.documentElement).appendChild(s),(e=(i=this._callbacks).onRuntimeInjected)==null||e.call(i)}catch{}}_resolveDirectTimelineAdapterFromWindow(i){var a,c;if(this.hasRuntimeBridge(i))return null;const e=Reflect.get(i,"__timelines");if(!z(e))return null;const t=Object.keys(e);if(t.length===0)return null;const s=(c=(a=this._iframe.contentDocument)==null?void 0:a.querySelector("[data-composition-id]"))==null?void 0:c.getAttribute("data-composition-id"),n=s&&s in e?s:t[t.length-1],o=e[n];return We(o)?o:null}_resolvePlaybackDurationAdapter(i){const e=Reflect.get(i,"__player");if(Ue(e))return{kind:"runtime",getDuration:()=>e.getDuration()};const t=this._resolveDirectTimelineAdapterFromWindow(i);return t?{kind:"direct-timeline",timeline:t,getDuration:()=>t.duration()}:null}}const Be=`
|
|
2
2
|
:host {
|
|
3
3
|
display: block;
|
|
4
4
|
position: relative;
|