refactor: remove Piper TTS, keep edge_tts only

- Remove piper_url from ReelRequest model and all function signatures
- Remove generate_tts_piper() function and Piper branch in generate_tts_with_subs()
- Remove /api/piper/config routes from server/routes.ts
- Remove piperConfig table, schemas and storage methods
- Remove piper_config migration
- Remove Piper TTS settings UI card
- Update "Piper TTS" labels to "TTS — voix activée"

edge_tts is now the only TTS engine, using precise word-boundary timing
This commit is contained in:
Michael committed 2026-05-19 10:55:01 +02:00
1 parent 8736e4f6a0
commit 59bc56aedc
12 files changed
+46 -265

No files matched your search

-12
View File
@@ -152,18 +152,6 @@ export async function migrate() {
);
`);
// piper_config
await client.query(`
CREATE TABLE IF NOT EXISTS "piper_config" (
"id" varchar PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"user_id" varchar NOT NULL REFERENCES "users"("id") ON DELETE cascade,
"url" text NOT NULL,
"created_at" timestamp DEFAULT now(),
"updated_at" timestamp DEFAULT now(),
CONSTRAINT piper_config_user_id_unique UNIQUE ("user_id")
);
`);
console.log("[Migration] Safe migration completed.");
} catch (error) {
console.error("[Migration] Error during migration:", error);
+1 -38
View File
@@ -10,7 +10,7 @@ import passport from "./auth";
import { z } from "zod";
import { openRouterService } from "./services/openrouter";
import { minioService as cloudinaryService, buildMinioUrl } from "./services/minio";
import { insertPostSchema, insertScheduledPostSchema, insertSocialPageSchema, insertAiGenerationSchema, insertCloudinaryConfigSchema, updateCloudinaryConfigSchema, insertOpenrouterConfigSchema, updateOpenrouterConfigSchema, insertPiperConfigSchema, updatePiperConfigSchema, insertUserSchema, postMedia, type SocialPage } from "@shared/schema";
import { insertPostSchema, insertScheduledPostSchema, insertSocialPageSchema, insertAiGenerationSchema, insertCloudinaryConfigSchema, updateCloudinaryConfigSchema, insertOpenrouterConfigSchema, updateOpenrouterConfigSchema, insertUserSchema, postMedia, type SocialPage } from "@shared/schema";
import type { User, InsertUser, ScheduledPost } from "@shared/schema";
import { analyticsRouter } from "./routes/analytics";
import { reelsRouter } from "./routes/reels";
@@ -423,43 +423,6 @@ export async function registerRoutes(app: Express): Promise<Server> {
// Analytics Routes
app.use("/api/analytics", requireAuth, analyticsRouter);
// Piper TTS Configuration Routes
app.get("/api/piper/config", requireAuth, async (req, res) => {
try {
const user = req.user as User;
const config = await storage.getPiperConfig(user.id);
if (!config) {
return res.json(null);
}
return res.json(config);
} catch (error) {
console.error("Error fetching Piper config:", error);
res.status(500).json({ error: "Failed to fetch configuration" });
}
});
app.post("/api/piper/config", requireAuth, async (req, res) => {
try {
const user = req.user as User;
const data = updatePiperConfigSchema.parse(req.body);
let config = await storage.getPiperConfig(user.id);
if (config) {
config = await storage.updatePiperConfig(user.id, data);
} else {
if (!data.url) {
return res.status(400).json({ error: "URL is required" });
}
config = await storage.createPiperConfig({ ...data as any, userId: user.id });
}
res.json({ success: true });
} catch (error) {
console.error("Error saving Piper config:", error);
res.status(500).json({ error: "Failed to save configuration" });
}
});
// Reels & Music Routes
app.use("/api", requireAuth, reelsRouter);
app.use("/api/remotion", requireAuth, remotionRouter);
+4 -17
View File
@@ -320,18 +320,12 @@ reelsRouter.post('/reels/preview', async (req: Request, res: Response) => {
const finalWordDuration = wordDuration;
let piperUrl: string | undefined;
if (ttsEnabled) {
const piperCfg = await storage.getPiperConfig(user.id);
piperUrl = piperCfg?.url ?? undefined;
}
// Traiter la vidéo via FFmpeg
const result = await ffmpegService.processReelFromUrl(resolveInternalUrl(media.originalUrl), {
text: overlayText,
musicUrl: finalMusicUrl,
ttsEnabled,
piperUrl,
ttsVoice,
wordDuration: finalWordDuration,
fontSize,
musicVolume,
@@ -370,8 +364,7 @@ reelsRouter.post('/reels/tts-preview', async (req: Request, res: Response) => {
return res.status(400).json({ error: 'Texte requis' });
}
const piperCfg = await storage.getPiperConfig(user.id);
const result = await ffmpegService.previewTTS(text, piperCfg?.url);
const result = await ffmpegService.previewTTS(text, voice);
if (!result.success) {
return res.status(500).json({ error: result.error || 'Erreur de génération TTS' });
@@ -550,19 +543,13 @@ async function processReelBackground(
const finalWordDuration = wordDuration ?? 0.6;
let piperUrlBg: string | undefined;
if (ttsEnabled) {
const piperCfg = await storage.getPiperConfig(userId);
piperUrlBg = piperCfg?.url ?? undefined;
}
console.log('🔊 [Background] TTS config:', { ttsEnabled, hasPiperUrl: !!piperUrlBg });
console.log('🔊 [Background] TTS config:', { ttsEnabled, ttsVoice });
const ffmpegResult = await ffmpegService.processReelFromUrl(resolveInternalUrl(media.originalUrl), {
text: overlayText,
musicUrl: finalMusicUrl,
ttsEnabled,
piperUrl: piperUrlBg,
ttsVoice,
wordDuration: finalWordDuration,
fontSize,
musicVolume,
+7 -8
View File
@@ -12,7 +12,7 @@ interface FFmpegReelRequest {
music_id?: string; // ID de la musique (catalogue FFmpeg)
music_url?: string; // OU URL directe de la musique
tts_enabled?: boolean; // Activation du TTS
piper_url?: string; // URL du serveur Piper TTS
tts_voice?: string; // Voix TTS (ex: fr-FR-VivienneNeural)
word_duration?: number; // Durée par mot (default: 0.6s)
font_size?: number; // Taille police (default: 24)
music_volume?: number; // Volume musique (default: 0.25)
@@ -71,7 +71,7 @@ export class FFmpegService {
musicId?: string;
musicUrl?: string;
ttsEnabled?: boolean;
piperUrl?: string;
ttsVoice?: string;
wordDuration?: number;
fontSize?: number;
musicVolume?: number;
@@ -90,7 +90,7 @@ export class FFmpegService {
music_id: options.musicId,
music_url: options.musicUrl,
tts_enabled: options.ttsEnabled,
piper_url: options.piperUrl,
tts_voice: options.ttsVoice,
word_duration: options.wordDuration ?? 0.6,
font_size: options.fontSize ?? 64,
music_volume: options.musicVolume ?? 0.25,
@@ -172,7 +172,7 @@ export class FFmpegService {
musicId?: string;
musicUrl?: string;
ttsEnabled?: boolean;
piperUrl?: string;
ttsVoice?: string;
wordDuration?: number;
fontSize?: number;
musicVolume?: number;
@@ -191,7 +191,7 @@ export class FFmpegService {
music_id: options.musicId,
music_url: options.musicUrl,
tts_enabled: options.ttsEnabled,
piper_url: options.piperUrl,
tts_voice: options.ttsVoice,
word_duration: options.wordDuration ?? 0.6,
font_size: options.fontSize ?? 64,
music_volume: options.musicVolume ?? 0.25,
@@ -216,7 +216,6 @@ export class FFmpegService {
hasMusicId: !!options.musicId,
hasMusicUrl: !!options.musicUrl,
ttsEnabled: options.ttsEnabled,
hasPiperUrl: !!options.piperUrl,
drawText: options.drawText,
});
@@ -291,7 +290,7 @@ export class FFmpegService {
}
async previewTTS(
text: string,
piperUrl?: string
ttsVoice?: string
): Promise<{ success: boolean; audioBase64?: string; error?: string }> {
const config = this.ensureConfigured();
@@ -305,7 +304,7 @@ export class FFmpegService {
body: JSON.stringify({
text,
tts_enabled: true,
piper_url: piperUrl,
tts_voice: ttsVoice,
}),
});
+1 -26
View File
@@ -31,9 +31,6 @@ import {
musicFavorites,
type MusicFavorite,
type InsertMusicFavorite,
piperConfig,
type PiperConfig,
type InsertPiperConfig,
audioTracks,
type AudioTrack,
type InsertAudioTrack,
@@ -120,15 +117,11 @@ export interface IStorage {
// Audio Tracks
getAudioTracks(): Promise<AudioTrack[]>;
// Audio Tracks
getAudioTrack(id: string): Promise<AudioTrack | undefined>;
createAudioTrack(track: InsertAudioTrack): Promise<AudioTrack>;
deleteAudioTrack(id: string): Promise<void>;
// Piper Config
getPiperConfig(userId: string): Promise<PiperConfig | undefined>;
createPiperConfig(config: InsertPiperConfig): Promise<PiperConfig>;
updatePiperConfig(userId: string, config: Partial<InsertPiperConfig>): Promise<PiperConfig>;
// App Config
getAppConfig(): Promise<AppConfig | undefined>;
upsertAppConfig(data: Partial<Pick<AppConfig, 'externalApiKey'>>): Promise<AppConfig>;
@@ -590,24 +583,6 @@ export class DatabaseStorage implements IStorage {
const [created] = await db.insert(appConfig).values(data).returning();
return created;
}
// Piper Config
async getPiperConfig(userId: string): Promise<PiperConfig | undefined> {
const [config] = await db.select().from(piperConfig).where(eq(piperConfig.userId, userId));
return config || undefined;
}
async createPiperConfig(config: InsertPiperConfig): Promise<PiperConfig> {
const [newConfig] = await db.insert(piperConfig).values(config).returning();
return newConfig;
}
async updatePiperConfig(userId: string, config: Partial<InsertPiperConfig>): Promise<PiperConfig> {
const [updated] = await db.update(piperConfig)
.set({ ...config, updatedAt: new Date() })
.where(eq(piperConfig.userId, userId))
.returning();
return updated;
}
}
export const storage = new DatabaseStorage();