mirror of
https://github.com/R0m1k3/Socialflow.git
synced 2026-10-12 01:36:54 +02:00
Service ffmpeg-api réécrit (ffmpeg-service/app/) : - Appels FFmpeg asynchrones : le service ne se fige plus pendant un rendu - Vidéo récupérée par téléchargement (GET /files/…) au lieu de base64 en JSON - Vraies erreurs HTTP ; échec explicite si la voix demandée est impossible - 30 voix Gemini + ton de lecture (dynamique, chaleureux, promo, calme), clé en en-tête, modèle configurable avec repli - Edge TTS 7 (minutage des mots restauré), secours en voix françaises - Voix traitée : filtre, compression, niveau constant ; musique bouclée et baissée automatiquement sous la voix ; mix final à -14 LUFS - Sous-titres calés mot à mot (Whisper pour Gemini et la voix d'origine, à la place de ffsubsync), style Montserrat, placés hors des boutons Reels - Vidéo : plus de retouche luminosité forcée, scaling lanczos, HDR iPhone converti, BT.709, AAC 48 kHz 192k ; la vidéo s'allonge si la voix dépasse - Grand logo de fin affiché après la voix ; FFmpeg 7.0.2 épinglé, polices et modèle Whisper intégrés à l'image ; tests pytest et ruff Application : - Sélecteur de voix partagé (4 pages) : moteur, 30 voix, ton, écoute - Reel images : minutage réel des mots, interrupteur voix respecté - sync-info ne génère plus de voix à chaque frappe (estimation locale) - Stabilisation désactivée par défaut, route /reels/preview inutilisée retirée - Log « [ReelQueue] Worker démarré » pour vérifier la version déployée Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018Ze4bs7tpF1KGWUk6ZZSZ4
79 lines
2.9 KiB
TypeScript
79 lines
2.9 KiB
TypeScript
/**
|
|
* Paramètres des rendus de Reels, validés à l'entrée des routes et stockés tels
|
|
* quels dans `reel_jobs.params` : un job repris après redémarrage retrouve ainsi
|
|
* exactement ce que l'utilisateur avait demandé.
|
|
*/
|
|
|
|
import { z } from "zod";
|
|
import { TTS_ENGINES, TTS_STYLES } from "./voices";
|
|
|
|
const optionalText = z
|
|
.string()
|
|
.trim()
|
|
.optional()
|
|
.transform((value) => (value ? value : undefined));
|
|
|
|
/** Reel construit à partir d'une vidéo de la médiathèque. */
|
|
export const videoReelParamsSchema = z.object({
|
|
videoMediaId: z.string({ required_error: "Vidéo requise" }).min(1, "Vidéo requise"),
|
|
pageIds: z
|
|
.array(z.string().min(1), { required_error: "Au moins une page requise" })
|
|
.min(1, "Au moins une page requise"),
|
|
musicTrackId: optionalText,
|
|
musicUrl: optionalText,
|
|
overlayText: optionalText,
|
|
description: optionalText,
|
|
ttsEnabled: z.boolean().default(false),
|
|
ttsVoice: optionalText,
|
|
ttsEngine: z.enum(TTS_ENGINES).optional(),
|
|
ttsStyle: z.enum(TTS_STYLES).optional(),
|
|
scheduledFor: optionalText,
|
|
// Ancien réglage, ignoré : le minutage vient désormais de la voix elle-même
|
|
wordDuration: z.number().optional(),
|
|
fontSize: z.number().int().min(16).max(200).default(64),
|
|
musicVolume: z.number().min(0).max(2).default(0.25),
|
|
drawText: z.boolean().default(true),
|
|
stabilize: z.boolean().default(false),
|
|
enableEndingEffect: z.boolean().default(true),
|
|
// Déterminé par le serveur à partir de la première page, jamais par le client
|
|
storeName: z.string().optional(),
|
|
});
|
|
|
|
export type VideoReelParams = z.infer<typeof videoReelParamsSchema>;
|
|
|
|
/** Reel construit à partir d'images (rendu Remotion). */
|
|
export const imagesReelParamsSchema = z.object({
|
|
// Chemins relatifs /uploads/... ou URL absolues
|
|
imageUrls: z.array(z.string().min(1)).min(1, "Aucune image fournie").max(4),
|
|
overlayText: optionalText,
|
|
musicUrl: optionalText,
|
|
musicVolume: z.number().min(0).max(2).default(0.3),
|
|
ttsEnabled: z.boolean().default(true),
|
|
ttsEngine: z.enum(TTS_ENGINES).optional(),
|
|
ttsVoice: optionalText,
|
|
ttsStyle: z.enum(TTS_STYLES).optional(),
|
|
storeName: z.string().optional(),
|
|
// Fichiers temporaires à supprimer une fois le rendu terminé
|
|
tempFiles: z.array(z.string()).default([]),
|
|
});
|
|
|
|
/** Aperçu de la voix. */
|
|
export const ttsPreviewSchema = z.object({
|
|
text: z.string({ required_error: "Texte requis" }).trim().min(1, "Texte requis").max(2000),
|
|
ttsVoice: optionalText,
|
|
ttsEngine: z.enum(TTS_ENGINES).optional(),
|
|
ttsStyle: z.enum(TTS_STYLES).optional(),
|
|
});
|
|
|
|
export type ImagesReelParams = z.infer<typeof imagesReelParamsSchema>;
|
|
|
|
export type ReelJobKind = "video" | "images";
|
|
|
|
export type ReelJobStatus = "pending" | "processing" | "completed" | "failed";
|
|
|
|
/** Résultat d'un rendu d'images, consulté par le client pour l'aperçu. */
|
|
export interface ImagesReelResult {
|
|
url: string;
|
|
thumbnailUrl: string | null;
|
|
}
|