mirror of
https://github.com/R0m1k3/Socialflow.git
synced 2026-10-21 14:08:10 +02:00
feat(reels): import d'un fichier SRT lu par la voix à son minutage
- Nouveau Reel (bureau et mobile) : bouton « Importer un fichier SRT ». Tant qu'un SRT est chargé, le texte libre et l'assistant IA sont désactivés ; le choix de la voix (moteur, voix, ton) reste disponible. - Service Python : chaque sous-titre est lu séparément, ses silences retirés, puis posé à son instant de début. Une lecture trop longue est accélérée (atempo, sans changer la hauteur) pour tenir dans la durée du sous-titre, avec un avertissement au-delà de ×1,35. Après un repli sur Edge, tous les sous-titres gardent la même voix. - Sans voix, les mots de chaque sous-titre s'affichent sur son intervalle. - Aperçu : sous-titres calés sur le minutage du fichier. - Tests : lecture SRT (TS), fenêtres, accélération et mixage (Python). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WVqEw4Xycgf8BtwmSftE2M
This commit is contained in:
15 files changed
+635
-44
No files matched your search
@@ -0,0 +1,54 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { parseSrt, srtEnd, srtText, srtWords } from "./srt";
|
||||
import { videoReelParamsSchema } from "./reel";
|
||||
|
||||
const SAMPLE = `1
|
||||
00:00:01,000 --> 00:00:03,500
|
||||
Bonjour à tous !
|
||||
|
||||
2
|
||||
00:00:04,000 --> 00:00:06,250
|
||||
<i>Découvrez</i> nos
|
||||
nouveautés en magasin
|
||||
|
||||
3
|
||||
00:00:07,000 --> 00:00:07,000
|
||||
Durée nulle, ignoré
|
||||
`;
|
||||
|
||||
describe("parseSrt", () => {
|
||||
it("lit les sous-titres, retire les balises et réunit les lignes", () => {
|
||||
const cues = parseSrt(SAMPLE.replace(/\n/g, "\r\n"));
|
||||
expect(cues).toEqual([
|
||||
{ start: 1, end: 3.5, text: "Bonjour à tous !" },
|
||||
{ start: 4, end: 6.25, text: "Découvrez nos nouveautés en magasin" },
|
||||
]);
|
||||
expect(srtText(cues)).toBe("Bonjour à tous ! Découvrez nos nouveautés en magasin");
|
||||
expect(srtEnd(cues)).toBe(6.25);
|
||||
});
|
||||
|
||||
it("accepte les millisecondes séparées par un point et sans numéro", () => {
|
||||
expect(parseSrt("00:00:02.5 --> 00:00:04.0\nSalut")).toEqual([{ start: 2.5, end: 4, text: "Salut" }]);
|
||||
});
|
||||
|
||||
it("refuse un fichier sans sous-titre", () => {
|
||||
expect(() => parseSrt("pas un srt")).toThrow(/Aucun sous-titre/);
|
||||
});
|
||||
|
||||
it("répartit les mots dans l'intervalle de leur sous-titre", () => {
|
||||
const words = srtWords(parseSrt(SAMPLE));
|
||||
const second = words.findIndex((w) => w.text === "Découvrez");
|
||||
expect(words[0].start).toBe(1);
|
||||
expect(words[second - 1].end).toBeCloseTo(3.5);
|
||||
expect(words[second].start).toBe(4);
|
||||
expect(words[words.length - 1].end).toBeCloseTo(6.25);
|
||||
});
|
||||
});
|
||||
|
||||
describe("videoReelParamsSchema avec SRT", () => {
|
||||
it("accepte des sous-titres valides et refuse une durée nulle", () => {
|
||||
const base = { videoMediaId: "v", pageIds: ["p"] };
|
||||
expect(videoReelParamsSchema.safeParse({ ...base, srtCues: [{ start: 0, end: 1, text: "a" }] }).success).toBe(true);
|
||||
expect(videoReelParamsSchema.safeParse({ ...base, srtCues: [{ start: 1, end: 1, text: "a" }] }).success).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user