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");