fix(cli): normalize the audio passage the clip plays (#5222)

This commit is contained in:
Abdullah M. Nazif
2026-10-08 21:29:17 +00:00
committed by GitHub
parent 1fac5fb29d
commit 1449d0d2a1
2 changed files with 43 additions and 6 deletions
@@ -13,6 +13,39 @@ const track = (integratedLufs: number, truePeakDbfs: number, volume = 1) => ({
truePeakDbfs,
});
describe("audioTags playback offsets", () => {
it.each([
['data-playback-start="4"', 4],
['data-playback-start="4" data-media-start="1"', 4],
['data-playback-start="0" data-media-start="4"', 0],
['data-playback-start="4" data-media-start="invalid"', 4],
['data-playback-start="-1" data-media-start="3"', 3],
['data-playback-start="1.5s" data-media-start="3"', 3],
['data-playback-start="" data-media-start="3"', 3],
['data-playback-start="invalid"', 0],
['data-media-start="3"', 3],
])("measures the selected in-point for %s", (attributes, expected) => {
const html = `<audio id="voice" src="voice.wav" data-duration="2" ${attributes}></audio>`;
expect(audioTags(html)[0]).toEqual(expect.objectContaining({ mediaStart: expected }));
});
it("measures a video from its playback in-point at its authored speed", () => {
const html = `<video id="talk" src="talk.mp4" data-playback-start="4" data-media-start="1" data-duration="2" data-playback-rate="2"></video>`;
const [tag] = audioTags(html);
expect(tag).toBeDefined();
if (!tag) throw new Error("missing video");
const args = loudnessMeasureArgs("talk.mp4", tag);
expect(args.slice(args.indexOf("-ss"), args.indexOf("-i"))).toEqual(["-ss", "4", "-t", "4"]);
});
it("preserves the selected in-point when writing the matched gain", () => {
const html = `<audio id="voice" src="voice.wav" data-playback-start="4" data-media-start="1" data-volume="1"></audio>`;
const changed = updateAudioVolume(html, "voice", 0.5);
expect(changed).toBe(html.replace('data-volume="1"', 'data-volume="0.5"'));
expect(audioTags(changed)[0]).toEqual(expect.objectContaining({ mediaStart: 4, volume: 0.5 }));
});
});
describe("audioTags with video", () => {
it("includes a video with sound as a normalizable clip", () => {
const html = `<video id="a-roll" src="talk.mp4" data-has-audio="true" data-duration="4"></video>`;
+10 -6
View File
@@ -4,6 +4,7 @@ import { promisify } from "node:util";
import { isAudibleVideoElement } from "@hyperframes/core/audible-video";
import { formatAudioGain, MAX_AUDIO_GAIN_DB } from "@hyperframes/core/audio-gain";
import { findFfBinary } from "@hyperframes/parsers/ff-binaries";
import { readMediaOffsetSeconds } from "@hyperframes/parsers/media-duration";
import { requestedProjectPath } from "./requestSubPath.js";
const execFileAsync = promisify(execFile);
@@ -253,6 +254,14 @@ function requiredAudioIdentity(attributes: Map<string, AttributeSpan>) {
return { id, src };
}
function authoredMediaStart(attributes: Map<string, AttributeSpan>, id: string): number {
return readMediaOffsetSeconds((name) => {
const raw = attributes.get(name)?.value;
if (name === "data-media-start") authoredNumber(raw, 0, name, id);
return raw;
});
}
/** Boolean attributes carry no `=`, so the value map cannot see them. */
function hasBareAttribute(html: string, range: TagRange, name: string): boolean {
const unquoted = html.slice(range.start, range.end).replace(/"[^"]*"|'[^']*'/g, '""');
@@ -298,12 +307,7 @@ function mediaTagFromRange(html: string, range: MediaTagRange): AudioTag | null
id,
src,
volume: authoredNumber(volumeAttribute?.value, 1, "data-volume", id),
mediaStart: authoredNumber(
attributes.get("data-media-start")?.value,
0,
"data-media-start",
id,
),
mediaStart: authoredMediaStart(attributes, id),
duration: authoredDuration(attributes, id),
playbackRate: authoredNumber(
attributes.get("data-playback-rate")?.value,