Merge pull request #310 from meiiie/fix/native-audio-lead

fix(recording): keep longer audio tracks anchored
This commit is contained in:
webadderall
2026-04-22 17:18:51 +10:00
committed by GitHub
2 changed files with 59 additions and 4 deletions
+50
View File
@@ -0,0 +1,50 @@
import { describe, expect, it } from "vitest";
import { appendSyncedAudioFilter, getAudioSyncAdjustment } from "./filters";
describe("getAudioSyncAdjustment", () => {
it("does not speed up longer audio tracks that would advance speech", () => {
expect(getAudioSyncAdjustment(120, 122.5)).toEqual({
mode: "none",
delayMs: 0,
tempoRatio: 1,
durationDeltaMs: -2500,
});
});
it("still stretches slightly shorter audio tracks to match the video", () => {
expect(getAudioSyncAdjustment(120, 117)).toEqual({
mode: "tempo",
delayMs: 0,
tempoRatio: 0.975,
durationDeltaMs: 3000,
});
});
it("still delays much shorter audio tracks instead of extreme tempo correction", () => {
expect(getAudioSyncAdjustment(120, 110)).toEqual({
mode: "delay",
delayMs: 10000,
tempoRatio: 1,
durationDeltaMs: 10000,
});
});
it("does not inject atempo when longer audio stays on the anchored path", () => {
const filterParts: string[] = [];
appendSyncedAudioFilter(filterParts, "[1:a]", "aout", getAudioSyncAdjustment(120, 122.5));
expect(filterParts).toEqual([
"[1:a]aresample=async=1:first_pts=0,asetpts=PTS-STARTPTS[aout]",
]);
});
it("still injects atempo for slightly shorter audio tracks", () => {
const filterParts: string[] = [];
appendSyncedAudioFilter(filterParts, "[1:a]", "aout", getAudioSyncAdjustment(120, 117));
expect(filterParts).toEqual([
"[1:a]atempo=0.975000,aresample=async=1:first_pts=0,asetpts=PTS-STARTPTS[aout]",
]);
});
});
+9 -4
View File
@@ -44,10 +44,17 @@ export function getAudioSyncAdjustment(
return { mode: "none", delayMs: 0, tempoRatio: 1, durationDeltaMs };
}
// When the recorded audio runs longer than the video, globally speeding it
// up can pull speech ahead of the picture. Keep the track anchored at the
// start instead and let the downstream mux path trim any trailing overrun.
if (durationDeltaMs < 0) {
return { mode: "none", delayMs: 0, tempoRatio: 1, durationDeltaMs };
}
const tempoRatio = Math.max(0.5, Math.min(2, audioDuration / videoDuration));
const relativeDelta = absDeltaMs / Math.max(videoDuration * 1000, 1);
if (relativeDelta <= 0.03 || absDeltaMs <= 1500 || durationDeltaMs < 0) {
if (relativeDelta <= 0.03 || absDeltaMs <= 1500) {
return { mode: "tempo", delayMs: 0, tempoRatio, durationDeltaMs };
}
@@ -78,9 +85,7 @@ export function formatFfmpegSeconds(milliseconds: number): string {
return (milliseconds / 1000).toFixed(3);
}
export function normalizePauseSegments(
pauseSegments: PauseSegment[] | undefined,
): PauseSegment[] {
export function normalizePauseSegments(pauseSegments: PauseSegment[] | undefined): PauseSegment[] {
if (!Array.isArray(pauseSegments) || pauseSegments.length === 0) {
return [];
}