mirror of
https://github.com/R0m1k3/Socialflow.git
synced 2026-10-11 17:26:45 +02:00
feat(reels): montage Remotion unifié, aperçu en direct, tests et CI
Montage (étape 3) : - Nouvelle composition ReelVideo : sous-titres animés mot à mot (3 styles : Impact, Surligné, Épuré), logo, effet de fin et fondu, en React - Le service Python prépare l'image (recadrage, HDR, stabilisation, dernière image figée) et la piste son finale (/prepare-reel) ; Remotion compose - Reels d'images sur les mêmes composants, avec le vrai minutage de la voix - Police Montserrat embarquée (plus de dépendance à Google Fonts) - REEL_RENDERER=ffmpeg conserve le rendu FFmpeg, plus rapide, en secours - Script de pré-bundle réparé (échouait en silence : require en ESM) Interface (étape 4) : - Aperçu en direct avec @remotion/player, identique au rendu final ; la voix testée cale les sous-titres, sinon minutage estimé - Choix du style de sous-titres sur les 4 pages Reel - Vraie progression : étape réelle du rendu, échecs visibles 24 h avec leur cause ; fin de la barre simulée et du faux « publié avec succès » Outillage (étape 5) : - Tests vitest (minutage identique à Python, validation, sécurité) et pytest ; CI GitHub Actions (tsc, tests, build, ruff) - Captures d'écran, out.mp4 et scripts de test retirés de la racine Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018Ze4bs7tpF1KGWUk6ZZSZ4
This commit is contained in:
64 files changed
+2488
-782
No files matched your search
@@ -0,0 +1,63 @@
|
||||
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 });
|
||||
});
|
||||
});
|
||||
|
||||
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 }]);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user