File size: 4,032 Bytes
bc50ea0 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | import assert from "node:assert/strict";
import fs from "node:fs";
import vm from "node:vm";
import {sceneLipSyncMode, applySceneLipSync} from "../web/h3_policy_core.mjs";
import {projectAudioTrackBindings, setProjectAudioTrack} from "../web/h3_project_asset_editor_core.mjs";
const shot = {id: "singing", prompt: "Keep me", length: 73};
assert.equal(sceneLipSyncMode(shot), "inherit");
applySceneLipSync(shot, "on");
assert.equal(sceneLipSyncMode(shot), "on");
assert.equal(shot.source_audio_target, "locked");
assert.equal(shot.source_reference, "off");
assert.equal(shot.generated_continuity, "off");
applySceneLipSync(shot, "off");
assert.equal(sceneLipSyncMode(shot), "off");
assert.equal(shot.source_audio_target, "off");
assert.equal(shot.source_reference, "off");
assert.equal(shot.generated_continuity, "off");
shot.source_reference = "on";
assert.equal(sceneLipSyncMode(shot), "custom");
applySceneLipSync(shot, "inherit");
assert.deepEqual(shot, {id: "singing", prompt: "Keep me", length: 73});
assert.throws(() => applySceneLipSync(shot, "custom"));
assert.equal(Object.hasOwn(shot, "final_audio"), false);
const asset = {id: "mix", options: {unrelated: true}};
assert.deepEqual(projectAudioTrackBindings(asset), {full_mix: "mix", vocals: "", instrumental: ""});
asset.options.audio_tracks = setProjectAudioTrack(asset, "vocals", "voice");
assert.deepEqual(projectAudioTrackBindings(asset), {full_mix: "mix", vocals: "voice", instrumental: ""});
asset.options.audio_tracks = setProjectAudioTrack(asset, "full_mix", "");
assert.deepEqual(projectAudioTrackBindings(asset), {full_mix: "", vocals: "voice", instrumental: ""});
assert.throws(() => setProjectAudioTrack(asset, "vocals", ""));
asset.options.audio_tracks = setProjectAudioTrack(asset, "instrumental", "voice");
assert.deepEqual(projectAudioTrackBindings(asset), {full_mix: "", vocals: "", instrumental: "voice"});
assert.equal(asset.options.unrelated, true);
assert.throws(() => setProjectAudioTrack(asset, "unknown", "voice"));
asset.options.audio_tracks = null;
assert.equal(projectAudioTrackBindings(asset).full_mix, "mix");
// Both production editors use the same policy helper and persistence paths;
// no new JSON field or shifted serialized widget is required.
for (const file of ["h3_chain_plan_studio.js", "h3_chain_plan_editor.js"]) {
const source = fs.readFileSync(new URL(`../web/${file}`, import.meta.url), "utf8");
assert.match(source, /applySceneLipSync\(shot, lipSync.value\)/);
assert.match(source, /lipSync.value = sceneLipSyncMode\(shot\)/);
assert.match(source, /Lip-sync/);
const start = source.indexOf('const lipSync = element("select");');
const stop = source.indexOf("function audioOverrideSelect", start);
const current = {id: "kept", prompt: "Untouched", length: 73};
const controls = [];
let saves = 0;
const sandbox = {
shot: current, planAudioPolicy: {sourceAudioTarget: "locked"},
applySceneLipSync, sceneLipSyncMode,
element: () => {
const control = {append() {}, addEventListener(event, callback) { this[event] = callback; }};
controls.push(control); return control;
},
form: {append() {}}, field() {},
syncPlan() { saves++; }, writePlan() { saves++; },
render() {}, renderScenePanel() {}, renderStatus() {},
};
vm.runInNewContext(source.slice(start, stop), sandbox);
const select = controls[0];
for (const mode of ["on", "off", "inherit"]) {
select.value = mode; select.change();
assert.equal(sceneLipSyncMode(current), mode);
}
assert.equal(saves, 3);
assert.deepEqual(current, {id: "kept", prompt: "Untouched", length: 73});
}
const carousel = fs.readFileSync(new URL("../web/h3_project_asset_manager.js", import.meta.url), "utf8");
assert.match(carousel, /setProjectAudioTrack\(asset, role, select.value\)/);
assert.match(carousel, /audio_tracks: null/);
console.log("Audio tracks UI: shared scene On/Off/Inheritance and safe Carousel role selectors pass");
|