mirror of
https://github.com/R0m1k3/Socialflow.git
synced 2026-10-14 18:58:30 +02:00
L'ancienne file comptait les posts « processing » : un redémarrage pendant un rendu la bloquait définitivement. Les rendus passent désormais par une table reel_jobs, réservée avec FOR UPDATE SKIP LOCKED, avec heartbeat et reprise des jobs interrompus au démarrage (2 tentatives maximum). - Pipeline vidéo et pipeline images (Remotion) extraits des routes vers server/services/reels/, les routes ne font que valider (zod) et mettre en file - Rendus d'images suivis en base au lieu d'une Map en mémoire - storeName conservé pour les jobs mis en attente - Publication Facebook en binaire : l'URL relative /uploads/... transmise auparavant n'était pas téléchargeable par Facebook - Job en échec si toutes les pages échouent ou si la voix demandée manque (le service Python renseigne enfin tts_error) - La voix choisie sur la page images est réellement utilisée - sync-info mesure la voix Gemini avec sa clé - Timeouts sur les appels au service FFmpeg, vignettes via execFile, /debug-ffmpeg protégé par la clé API, plus de voix anglaise en secours Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018Ze4bs7tpF1KGWUk6ZZSZ4
66 lines
2.2 KiB
TypeScript
66 lines
2.2 KiB
TypeScript
/**
|
|
* Résolution des ressources d'un Reel (musique, logo, clé Gemini, nom du
|
|
* magasin), jusqu'ici recopiée dans chaque route.
|
|
*/
|
|
|
|
import { storage } from "../../storage";
|
|
import { buildMinioUrl, resolveInternalUrl } from "../minio";
|
|
|
|
/**
|
|
* URL de la musique téléchargeable par le service FFmpeg (réseau Docker
|
|
* interne : l'URL publique HTTPS n'y est pas joignable).
|
|
*/
|
|
export async function resolveMusicUrl(
|
|
musicTrackId?: string,
|
|
musicUrl?: string,
|
|
): Promise<string | undefined> {
|
|
if (musicUrl) return resolveInternalUrl(musicUrl);
|
|
if (!musicTrackId?.startsWith("internal_")) return undefined;
|
|
|
|
try {
|
|
const track = await storage.getAudioTrack(musicTrackId.slice("internal_".length));
|
|
return track ? resolveInternalUrl(track.url) : undefined;
|
|
} catch (error) {
|
|
console.error("⚠️ [Reels] Piste audio introuvable :", error);
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
/** Chemin relatif (/uploads/...) du logo configuré, s'il existe. */
|
|
export async function resolveLogoPath(): Promise<string | undefined> {
|
|
try {
|
|
const config = await storage.getCloudinaryConfig();
|
|
if (config?.logoPublicId) {
|
|
return buildMinioUrl(config.cloudName, config.logoPublicId, config.publicUrl);
|
|
}
|
|
} catch (error) {
|
|
console.error("⚠️ [Reels] Configuration du logo illisible :", error);
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
/** Clé Gemini : configuration de l'application, sinon variable d'environnement. */
|
|
export async function resolveGeminiApiKey(ttsEngine?: string): Promise<string | undefined> {
|
|
if (ttsEngine !== "gemini") return undefined;
|
|
const appConfig = await storage.getAppConfig();
|
|
return appConfig?.geminiApiKey ?? process.env.GEMINI_API_KEY ?? undefined;
|
|
}
|
|
|
|
/** Nom affiché dans l'outro : celui de la page choisie, sinon de la première page de l'utilisateur. */
|
|
export async function resolveStoreName(
|
|
userId: string,
|
|
preferredPageId?: string,
|
|
): Promise<string | undefined> {
|
|
try {
|
|
if (preferredPageId) {
|
|
const page = await storage.getSocialPage(preferredPageId);
|
|
if (page) return page.pageName;
|
|
}
|
|
const pages = await storage.getSocialPages(userId);
|
|
return pages[0]?.pageName;
|
|
} catch (error) {
|
|
console.error("⚠️ [Reels] Nom du magasin introuvable :", error);
|
|
return undefined;
|
|
}
|
|
}
|