- ttsSyncService.calculateSyncTiming now accepts ttsEngine parameter
- sync-info route extracts ttsVoice/ttsEngine from req.body instead of voice
- Previously it always fell back to Edge TTS even when Gemini was selected
(because server stored 'fr-FR-Standard-B' but route only used 'voice')
- Store Gemini API key globally in appConfig (single key for all users)
- Add /api/settings/gemini GET/POST/DELETE routes for API key management
- Backend: add tts_engine parameter ("edge" or "gemini") to FFmpeg service
- Backend: add generate_tts_gemini() using Google Cloud TTS REST API
- Frontend: Settings page shows Google Gemini API key input card
- Frontend: new-reel, mobile/new-reel, remotion-video, mobile/remotion-video
pages now have Edge/Gemini engine toggle and French voice selector
- Fix tts-preview route to extract ttsVoice from req.body instead of
undefined voice variable
- 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
Remove Minimax Speech API and Freesound integrations entirely.
Add Piper TTS as the sole TTS provider via configurable HTTP URL.
- Add piper_config DB table (url field, per-user)
- Add GET/POST /api/piper/config routes
- Python: replace generate_tts_minimax with generate_tts_piper (GET ?text=, WAV→MP3)
- Simplify generate_tts_with_subs: piper_url param replaces tts_provider+minimax fields
- Drop ttsVoice/ttsProvider from all UI, API, and background job params
- Settings page: replace Minimax+Freesound cards with single Piper URL input
- Remove freeSoundService init from server startup and CSP headers
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pass GroupId query param to Minimax T2A v2 API — required for paid
plan quota allocation. Without it, Minimax defaults to (0/0 used).
- shared/schema.ts: groupId field on minimaxConfig table
- server/migrate.ts: ADD COLUMN IF NOT EXISTS group_id
- server/routes/reels.ts: fetch and pass groupId alongside apiKey
- server/services/ffmpeg.ts: minimax_group_id in request interface/body
- ffmpeg-service/main.py: GroupId in URL, threaded through all call sites
- client/src/pages/settings.tsx: Group ID input in Minimax settings card
- Python: capture tts_error_msg on exception, return in response
- Python: log tts_provider, minimax_api_key presence before call
- ffmpeg.ts: read tts_error from response, log and return it
- reels.ts: store TTS error in post.generationError for visibility
- Add minimax_config table (migration + schema + storage CRUD)
- Add GET/POST /api/minimax/config routes
- Pass tts_provider + minimax_api_key through ffmpeg service
- Add generate_tts_minimax() in Python using Minimax T2A v2 API
- Fall back to ffsubsync for subtitle sync (no WordBoundary events)
- Add Minimax config card in Settings page
- Add provider toggle (Edge TTS / Minimax) + French voices in new-reel
- facebook.ts: use resolvePublicUrl + getMediaBuffer for reels so missing local files fall back to HTTP fetch.
- scheduler.ts: only delete local video files after the last pending scheduled post for that postId is published.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- ffmpeg-service/main.py: replace ffsubsync path with exact word-boundary timing from edge_tts for TTS subtitles. Karaoke styling preserved.
- server/services/ttsSync.ts: fix word count to match TTS-cleaned text, remove artificial punctuationPause subtraction, strip punctuation tokens from count.
- server/routes/reels.ts: remove redundant ttsSyncService calls in preview/background; word_duration is ignored by Python, these only wasted TTS generations.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Calculate optimal word_duration based on actual TTS audio duration and text punctuation.
- server/services/ttsSync.ts: new service to measure TTS audio and compute sync timing
- server/routes/reels.ts: integrate sync into preview and background processing
- client: auto-calculate sync info widget in desktop and mobile Reel creation
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Facebook resumable upload START/FINISH phases require multipart/form-data
(not application/x-www-form-urlencoded). Add full raw response logging for
each phase to diagnose any remaining issues (subcode, etc).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace simple multipart upload (which fails with error 6000 on large
files) with the 3-phase Resumable Upload API (start → transfer → finish).
This is the recommended Facebook approach for files > ~50 MB and is much
more reliable regardless of file size.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add slow zoom/pan (Ken Burns) effect to each image slide, cycling through
8 deterministic presets per image index for natural variety
- CapCut-style captions: semi-transparent pill background + yellow glow on
active word; background/text now only visible while voice is speaking
- Fix Facebook error 6000: pass Node.js Buffer directly instead of unsafe
ArrayBuffer pool-slice conversion which could corrupt the upload payload
- Fix TypeScript error: replace u-flag emoji regex with BMP surrogate pairs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Media files were stored with absolute http://localhost:5555/... URLs,
which got blocked by the Content Security Policy when the app runs at
https://socialflow.fnancy.fr. Now stores relative /uploads/... paths
and resolves to absolute URLs only when needed by external APIs
(Facebook, FFmpeg internal service).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Media is now stored directly on the server disk (no external service).
Files are served via Express static middleware at /uploads/*.
Cleanup rules:
- Videos deleted immediately after successful publish to Facebook/Instagram
- Videos older than 7 days purged daily by cron job
- Images older than 30 days purged daily by cron job
Changes:
- server/services/minio.ts: replaced S3 client with local fs read/write
- server/index.ts: added static serving for /uploads/media, /logos, /stories
- server/services/media-purge.ts: new rules (images 30d, videos 7d)
- server/services/scheduler.ts: delete local videos after successful publish
- docker-compose.yml: add media_uploads, logos_uploads, stories_uploads volumes
- settings UI: removed credentials card, shows local storage info
- package.json: removed googleapis (unneeded)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Store endpointUrl and publicUrl in cloudinary_config DB table so
MinIO connection can be fully configured from the app settings
without needing environment variables.
- DB migration: adds endpoint_url and public_url columns
- Schema: adds endpointUrl/publicUrl to cloudinaryConfig table
- MinIO service: reads endpoint from DB (falls back to MINIO_ENDPOINT env)
- buildMinioUrl: accepts optional publicUrl override from DB config
- All routes updated to pass config.publicUrl to buildMinioUrl
- Settings UI (desktop + mobile): adds Endpoint URL and Public URL fields
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Create server/services/minio.ts: S3-compatible service (same interface as
cloudinaryService) using @aws-sdk/client-s3 and MINIO_ENDPOINT env var
- Replace all cloudinaryService imports with minioService across routes.ts,
routes/reels.ts, routes/remotion.ts, services/media-purge.ts
- Replace Cloudinary logo URL pattern (res.cloudinary.com/...) with
buildMinioUrl() helper throughout all server routes
- GET /api/cloudinary/config now returns a logoUrl field (full MinIO URL)
so clients never need to build the URL themselves
- Update settings pages (desktop + mobile): labels changed to MinIO
(Bucket Name, Access Key, Secret Key)
- Update image-editor.tsx: remove Cloudinary URL transformation, use logoUrl
- Update media-utils.ts: simplify getVideoThumbnailUrl (no URL transforms)
- Add MINIO_ENDPOINT and MINIO_PUBLIC_URL to docker-compose.yml + .env.example
- DB table reuse: cloudinary_config.cloud_name = bucket, api_key = access key,
api_secret = secret key — no migration needed
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>