fix(274): end the fade-out where the black hold starts

FADE_OUT_POST holds full black over the last stretch of the video, but the ramp
still aimed to finish at lastFrameS - FADE_OUT_S, so it completed inside the
hold and the two overlapped. The ramp now runs from
lastFrameS - FADE_OUT_POST - FADE_OUT_S to lastFrameS - FADE_OUT_POST, giving
FADE_OUT_S of fade followed by FADE_OUT_POST of black.

Verified: with 0.4 / 0.3 / 0.2 / 0.4 the picture is clear from 0.70 s to
618.67 s, and the rendered file is pure black for the first and last 13 frames
at 30 fps (25 at 60).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PLt25nCVMsoroKALYUBu6u
This commit is contained in:
2026-09-30 01:29:13 +09:00
parent 2dfd52e33d
commit d7b3824e1e
+5 -1
View File
@@ -5,6 +5,8 @@
/** Seconds of complete black at start */
export const FADE_IN_PRE = 0.4;
/** Seconds of complete black at end */
export const FADE_OUT_POST = 0.4;
/** Seconds from black to the picture at the start. 0 disables the fade-in. */
export const FADE_IN_S = 0.3;
/** Seconds from the picture to black at the end. 0 disables the fade-out. */
@@ -19,6 +21,8 @@ const clamp01 = (v: number) => Math.min(1, Math.max(0, v));
*/
export const blackOpacity = (t: number, lastFrameS: number): number => {
const fadeIn = (t < FADE_IN_PRE) ? 1 : (FADE_IN_S > 0 ? 1 - clamp01((t - FADE_IN_PRE) / FADE_IN_S) : 0);
const fadeOut = FADE_OUT_S > 0 ? clamp01((t - (lastFrameS - FADE_OUT_S)) / FADE_OUT_S) : 0;
// Black from here to the last frame; the fade-out ramp ends exactly here.
const blackFromS = lastFrameS - FADE_OUT_POST;
const fadeOut = (t >= blackFromS) ? 1 : (FADE_OUT_S > 0 ? clamp01((t - (blackFromS - FADE_OUT_S)) / FADE_OUT_S) : 0);
return Math.max(fadeIn, fadeOut);
};