mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-09-25 07:16:02 +00:00
fix(audio-sync): cap leading-silence delay at 15s, anchor and pad-end above
When a recorded audio file is significantly shorter than the captured video, getAudioSyncAdjustment previously returned mode "delay" for any delta above 3% relative AND 1500ms absolute, prepending leading silence equal to the full duration delta via `adelay=N|N`. For long recordings where the duration delta can grow to tens of seconds or minutes, issue #252 reports up to 2 minutes of pure silence at the start of a 10-min Windows recording before audio cuts in mid-clip. Cap the delay branch at 15s. Above the cap, return a new "pad" mode that anchors the audio at the start and emits `apad=pad_dur=...` so the audio plays from t=0 and trailing silence fills the rest of the video duration. Users perceive this as "audio recording cut out before video ended" rather than "audio is shifted late vs video", which is the clearer failure mode when the underlying capture produced a short audio file. Owner's existing assertion that 10s short audio gets delay (test #3 in filters.test.ts: getAudioSyncAdjustment(120, 110) -> delay 10000) is preserved unchanged because 10000 <= 15000. Refs: #252 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
f629cbf992
commit
2573308f79
@@ -47,4 +47,47 @@ describe("getAudioSyncAdjustment", () => {
|
||||
"[1:a]atempo=0.975000,aresample=async=1:first_pts=0,asetpts=PTS-STARTPTS[aout]",
|
||||
]);
|
||||
});
|
||||
|
||||
it("anchors and pads at end when the duration delta exceeds the delay cap (long recording case)", () => {
|
||||
// Reporter case in issue #252: 10-min video with audio probed as ~8 min.
|
||||
// Previously this returned mode "delay" with delayMs 120000, prepending
|
||||
// 2 minutes of leading silence via `adelay`. That is jarring on long
|
||||
// recordings. Above the cap we anchor at the start instead.
|
||||
expect(getAudioSyncAdjustment(600, 480)).toEqual({
|
||||
mode: "pad",
|
||||
delayMs: 0,
|
||||
tempoRatio: 1,
|
||||
durationDeltaMs: 120000,
|
||||
});
|
||||
});
|
||||
|
||||
it("anchors and pads at end when delta is just over the delay cap", () => {
|
||||
// 35-second short on a 10-min recording — the earlier symptom on issue
|
||||
// #252 before the regression to ~2 min. Above the 15s cap, anchor.
|
||||
expect(getAudioSyncAdjustment(600, 565)).toEqual({
|
||||
mode: "pad",
|
||||
delayMs: 0,
|
||||
tempoRatio: 1,
|
||||
durationDeltaMs: 35000,
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps the existing delay branch for moderately short audio at the cap boundary", () => {
|
||||
// Boundary: 15-second short stays in delay mode (cap is strictly >).
|
||||
expect(getAudioSyncAdjustment(120, 105)).toEqual({
|
||||
mode: "delay",
|
||||
delayMs: 15000,
|
||||
tempoRatio: 1,
|
||||
durationDeltaMs: 15000,
|
||||
});
|
||||
});
|
||||
|
||||
it("emits apad with trailing silence for the pad mode instead of leading adelay", () => {
|
||||
const filterParts: string[] = [];
|
||||
appendSyncedAudioFilter(filterParts, "[1:a]", "aout", getAudioSyncAdjustment(600, 480));
|
||||
|
||||
expect(filterParts).toEqual([
|
||||
"[1:a]apad=pad_dur=120.000,aresample=async=1:first_pts=0,asetpts=PTS-STARTPTS[aout]",
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
import type { AudioSyncAdjustment, PauseSegment } from "../types";
|
||||
|
||||
// When the audio file is significantly shorter than the video, prepending
|
||||
// leading silence equal to the full duration delta (the `delay` branch) is
|
||||
// reasonable for small deltas but becomes very jarring at tens of seconds:
|
||||
// the user hears a long silence before audio cuts in mid-clip. Above this
|
||||
// cap, anchor audio at the start and pad trailing silence instead.
|
||||
const MAX_AUDIO_SYNC_DELAY_MS = 15000;
|
||||
|
||||
export function buildAtempoFilters(tempoRatio: number): string[] {
|
||||
if (!Number.isFinite(tempoRatio) || tempoRatio <= 0) {
|
||||
return [];
|
||||
@@ -58,6 +65,10 @@ export function getAudioSyncAdjustment(
|
||||
return { mode: "tempo", delayMs: 0, tempoRatio, durationDeltaMs };
|
||||
}
|
||||
|
||||
if (durationDeltaMs > MAX_AUDIO_SYNC_DELAY_MS) {
|
||||
return { mode: "pad", delayMs: 0, tempoRatio: 1, durationDeltaMs };
|
||||
}
|
||||
|
||||
return { mode: "delay", delayMs: durationDeltaMs, tempoRatio: 1, durationDeltaMs };
|
||||
}
|
||||
|
||||
@@ -77,6 +88,10 @@ export function appendSyncedAudioFilter(
|
||||
filters.push(...buildAtempoFilters(adjustment.tempoRatio));
|
||||
}
|
||||
|
||||
if (adjustment.mode === "pad" && adjustment.durationDeltaMs > 0) {
|
||||
filters.push(`apad=pad_dur=${(adjustment.durationDeltaMs / 1000).toFixed(3)}`);
|
||||
}
|
||||
|
||||
filters.push("aresample=async=1:first_pts=0", "asetpts=PTS-STARTPTS");
|
||||
filterParts.push(`${inputLabel}${filters.join(",")}[${outputLabel}]`);
|
||||
}
|
||||
|
||||
@@ -156,7 +156,7 @@ export type UiohookModuleNamespace = {
|
||||
};
|
||||
|
||||
export type AudioSyncAdjustment = {
|
||||
mode: "none" | "tempo" | "delay";
|
||||
mode: "none" | "tempo" | "delay" | "pad";
|
||||
delayMs: number;
|
||||
tempoRatio: number;
|
||||
durationDeltaMs: number;
|
||||
|
||||
Reference in New Issue
Block a user