fix(talking-head): preserve source audio (#2260)

This commit is contained in:
Miguel Ángel
2026-07-16 11:30:49 -04:00
committed by GitHub
parent b8a44e49c9
commit 335e7483b5
3 changed files with 51 additions and 2 deletions
+2 -2
View File
@@ -74,8 +74,8 @@
"files": 2
},
"talking-head-recut": {
"hash": "0f365480fb5bfa5b",
"files": 27
"hash": "0d0ede12041c51b7",
"files": 28
}
}
}
+14
View File
@@ -938,6 +938,15 @@ ffmpeg -y -i "$VIDEO_PATH" -c:v libx264 -crf 18 -g 30 -keyint_min 30 \
data-track-index="1"
></video>
</div>
<!-- Preserve the source program audio while the visual video stays muted. -->
<audio
id="source-audio"
src="input-video.mp4"
data-start="0"
data-duration="121.2"
data-track-index="10"
data-volume="1"
></audio>
<!-- Layer 2: each card-host sits at the bounds dictated by its layout. -->
<!-- IMPORTANT: every card-host MUST carry BOTH "card-host" and "clip" classes. -->
@@ -1164,6 +1173,11 @@ PRODUCER_BROWSER_GPU_MODE=hardware npx hyperframes render public \
```
`hyperframes render <dir>` reads `<dir>/index.html` and produces the MP4.
The canonical composition keeps the visual `<video>` muted and mounts the same
source as the root `#source-audio` track, so the rendered MP4 preserves the
talking-head audio without a manual remux. This uses a separate audio track
rather than `data-has-audio="true"` so its volume and ducking remain independently
controllable on the timeline.
The flag `PRODUCER_BROWSER_GPU_MODE=hardware` (or `--browser-gpu`) is
strongly recommended on macOS — software-only Chrome rendering times out
on most laptops.
@@ -0,0 +1,35 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";
const skill = readFileSync(new URL("./SKILL.md", import.meta.url), "utf8");
function findTag(tagName, id) {
const match = skill.match(new RegExp(`<${tagName}\\b[^>]*\\bid="${id}"[^>]*>`, "i"));
assert.ok(match, `expected <${tagName} id="${id}"> in the canonical composition`);
return match[0];
}
function readAttribute(tag, attribute) {
const match = tag.match(new RegExp(`\\b${attribute}="([^"]+)"`, "i"));
assert.ok(match, `expected ${attribute} on ${tag}`);
return match[1];
}
test("canonical composition preserves source audio as a root media track", () => {
const video = findTag("video", "bg-video");
const audio = findTag("audio", "source-audio");
assert.match(video, /\bmuted\b/);
assert.match(
skill,
/<\/div>\s*<!-- Preserve the source program audio[\s\S]*?<audio\b[^>]*\bid="source-audio"[^>]*>[\s\S]*?<\/audio>/,
);
assert.equal(readAttribute(audio, "src"), readAttribute(video, "src"));
assert.equal(readAttribute(audio, "data-start"), readAttribute(video, "data-start"));
assert.equal(readAttribute(audio, "data-duration"), readAttribute(video, "data-duration"));
assert.notEqual(
readAttribute(audio, "data-track-index"),
readAttribute(video, "data-track-index"),
);
});