mirror of
https://github.com/R0m1k3/Socialflow.git
synced 2026-10-12 01:36:54 +02:00
Fins de phrases coupées : - Le fondu final ne commence plus avant la fin de la voix (il rognait la dernière phrase quand la vidéo finissait peu après) ; il se raccourcit au besoin. Même règle côté Python, aperçu et rendu Remotion - SRT : découpe au milieu de la zone la plus calme de chaque pause, mesurée sur l'enveloppe sonore (tranches de 10 ms), avec un seuil relatif au niveau de la voix qui respecte les fins douces ; 300 ms gardées après le dernier son Bruits parasites : - Normalisation de la voix en deux passes, en mode linéaire : le mode dynamique relevait souffles et bruits entre les phrases (mesuré : -64 dB avant, -75 dB après dans les pauses) - Réduction de bruit douce (afftdn), compresseur sans gain ajouté, fondus de 5 ms aux extrémités ; mix final moins « pompant » (LRA 20) - Voix Gemini vérifiée : les bruits ajoutés avant le premier ou après le dernier mot sont retirés Vérification de lecture comparée lettre à lettre : les petites erreurs de Whisper ne déclenchent plus de régénérations inutiles. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018Ze4bs7tpF1KGWUk6ZZSZ4
73 lines
2.9 KiB
TypeScript
73 lines
2.9 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
cleanCaptionText,
|
|
computeImagesTiming,
|
|
computeReelTiming,
|
|
groupLines,
|
|
offsetWords,
|
|
spreadWords,
|
|
} from "./captions";
|
|
|
|
// Mêmes cas que ffmpeg-service/tests/test_render.py : l'aperçu et le rendu
|
|
// doivent tomber sur les mêmes durées.
|
|
describe("computeReelTiming", () => {
|
|
it("allonge la vidéo quand la voix dépasse", () => {
|
|
expect(computeReelTiming({ videoDuration: 5, voiceDuration: 10, hasOutro: false, endingEffect: true }).total).toBe(12.8);
|
|
});
|
|
|
|
it("réserve l'effet de fin après la voix", () => {
|
|
const timing = computeReelTiming({ videoDuration: 6, voiceDuration: 4.8, hasOutro: true, endingEffect: true });
|
|
expect(timing.total).toBe(9.3);
|
|
expect(timing.logoStart).toBeCloseTo(6.8);
|
|
expect(timing.fadeStart).toBeCloseTo(7.3);
|
|
});
|
|
|
|
it("n'a ni logo ni fondu sans effet de fin", () => {
|
|
const timing = computeReelTiming({ videoDuration: 12, hasOutro: true, endingEffect: false });
|
|
expect(timing).toEqual({ total: 12, logoStart: null, fadeStart: null, fadeDuration: null });
|
|
});
|
|
});
|
|
|
|
describe("fondu final", () => {
|
|
it("ne commence jamais avant la fin de la voix (mêmes valeurs que Python)", () => {
|
|
const timing = computeReelTiming({ videoDuration: 4, voiceDuration: 5, hasOutro: false, endingEffect: true });
|
|
expect(timing.total).toBe(7.8);
|
|
expect(timing.fadeStart).toBeGreaterThanOrEqual(7);
|
|
expect(timing.fadeDuration).toBeGreaterThanOrEqual(0.3);
|
|
});
|
|
});
|
|
|
|
describe("computeImagesTiming", () => {
|
|
it("garde un Reel d'images entre 25 et 30 s", () => {
|
|
expect(computeImagesTiming({ imageCount: 3, hasEnding: true })).toEqual({ total: 25, endingSeconds: 3 });
|
|
expect(computeImagesTiming({ imageCount: 4, voiceDuration: 40, hasEnding: true }).total).toBe(30);
|
|
expect(computeImagesTiming({ imageCount: 4, voiceDuration: 24, hasEnding: false }).total).toBe(25);
|
|
});
|
|
});
|
|
|
|
describe("groupLines", () => {
|
|
const words = (texts: string[]) => texts.map((text, i) => ({ text, start: i, end: i + 0.5 }));
|
|
|
|
it("coupe à la ponctuation et limite chaque ligne", () => {
|
|
const lines = groupLines(words(["Bonjour,", "venez", "découvrir", "nos", "nouveautés"]));
|
|
expect(lines[0].map((w) => w.text)).toEqual(["Bonjour,"]);
|
|
expect(lines.every((line) => line.length <= 3)).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("texte des sous-titres", () => {
|
|
it("retire hashtags, emojis et liens", () => {
|
|
expect(cleanCaptionText("Promo 🌸 #AménagementExtérieur ici https://x.fr !")).toBe("Promo ici !");
|
|
});
|
|
|
|
it("répartit l'estimation sur l'intervalle", () => {
|
|
const spread = spreadWords("un deux trois", 2, 5);
|
|
expect(spread[0].start).toBe(2);
|
|
expect(spread[spread.length - 1].end).toBeCloseTo(5);
|
|
});
|
|
|
|
it("décale les mots de la voix vers le temps de la vidéo", () => {
|
|
expect(offsetWords([{ text: "a", start: 0.1, end: 0.4 }], 2)).toEqual([{ text: "a", start: 2.1, end: 2.4 }]);
|
|
});
|
|
});
|