Deux corrections demandées ensemble.
1. Vignettes vidéo
Le planificateur supprime la vidéo locale une fois publiée, alors que la
ligne `media` survit pour l'historique. L'interface tentait donc de lire
un fichier disparu pour en extraire une image : d'où les avertissements
« Video thumbnail generation failed » et les tuiles vides.
Une vignette JPEG est désormais extraite à la création du reel, dans les
deux flux de génération, et stockée à part : elle reste disponible quand
la vidéo ne l'est plus. La purge quotidienne la nettoie en même temps que
la ligne qu'elle illustre. L'extraction est tolérante à l'échec — une
vignette manquante dégrade l'affichage mais ne fait jamais échouer une
publication — et le composant retombe sur son icône de remplacement pour
les médias créés avant ce changement. La fonction d'extraction, jusque-là
privée à la route Remotion, devient un service partagé.
2. Erreurs de compilation préexistantes (27 → 0)
- remotion-video.tsx (bureau et mobile) : les composants Select étaient
utilisés sans être importés, ce qui faisait planter le sélecteur de voix
à l'exécution.
- external.ts : fileSize et aiGenerated étaient transmis dans un type que
le schéma ne déclare pas, et ne fonctionnaient que par conversion
implicite de PostgreSQL.
- reels.ts : l'interface MusicTrack, référencée mais jamais déclarée dans
ce module.
- remotion.ts : chromiumOptions.args retiré. Remotion ne lit jamais cette
clé, les drapeaux Chrome qu'elle contenait n'atteignaient donc pas le
navigateur.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018uUPZ1GNpEkYB1dpXHYPek
La route /api/ai/generate connaissait la cause exacte de l'échec
(configuration absente, clé invalide, crédits épuisés, modèle inconnu)
mais la remplaçait par un « Failed to generate text » en 500, et
l'interface affichait de son côté un message figé. Sans accès aux logs
du serveur, l'utilisateur n'avait donc aucun moyen de savoir quoi
corriger.
Le motif renvoyé par OpenRouter est désormais extrait, tronqué et
propagé jusqu'au bandeau d'erreur, avec un code HTTP qui distingue un
problème de configuration (400) d'un refus du service tiers (502).
La clé API n'apparaît à aucun moment dans ces messages.
Même traitement pour /api/reels/generate-text, qui souffrait du même
masquage.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018uUPZ1GNpEkYB1dpXHYPek
Ajoute la gestion multi-comptes TikTok et permet d'envoyer la même vidéo
sur plusieurs pages Facebook ET plusieurs comptes TikTok en une seule
publication.
Un compte TikTok vit dans social_pages comme une page Facebook : il hérite
donc des permissions par utilisateur, de la planification et du calendrier.
Trois particularités de l'API TikTok structurent l'implémentation :
- pas de jeton saisi à la main : chaque compte passe par OAuth, et
l'access token (24h) est renouvelé via le refresh token (1 an) avant
chaque publication ;
- la publication est asynchrone : l'upload rend un publish_id, et un
poller récupère l'identifiant définitif du post ;
- creator_info doit être interrogé avant chaque envoi pour ne demander
qu'un niveau de confidentialité réellement autorisé sur le compte.
Serveur :
- service TikTok (OAuth, creator_info, upload FILE_UPLOAD par chunks,
suivi de statut) et routes de connexion/configuration
- schéma : platform 'tiktok', refresh token et scopes sur social_pages,
publish_id/publish_status sur scheduled_posts, table tiktok_config
- routage par plateforme dans les deux flux de reels et le planificateur
- cron de suivi des publications, token manager et analytics adaptés
Client :
- connexion et reconnexion des comptes TikTok depuis « Pages gérées »
- sélection combinée pages Facebook / comptes TikTok à la publication
- configuration de l'application TikTok dans les paramètres (admin)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018uUPZ1GNpEkYB1dpXHYPek
The SPA's session check (/api/auth/session) is cached indefinitely by
react-query (staleTime: Infinity, no refetch on focus/interval), so once a
protected page mounts successfully, the app never re-verifies auth. If the
server-side session later becomes invalid (server restart with in-memory
sessions, cookie/session expiry, etc.), every subsequent API call
(pages, scheduled-posts, audio-tracks, media, media/upload, ...) starts
failing with 401 in a loop with no way for the user to recover short of a
manual page reload.
Add a shared handleUnauthorized() in queryClient.ts that redirects to
/login on any non-auth API 401, wired into both the shared fetch helpers
(apiRequest/getQueryFn) and the raw fetch() calls used for file uploads
and Remotion rendering, which also lacked this recovery path. Also add the
missing credentials: "include" to those raw fetch() calls for consistency
with the rest of the app's API requests.
Replace the 8-item Google Cloud TTS voice list with 2 Gemini native
voices: Charon (homme) and Kore (femme). Set Charon as the default
when switching to Gemini engine in all four reel pages.
https://claude.ai/code/session_01QBvwHAMZzVYfWvy1U5paat
- 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>
Calendar now updates instantly without page reload after creating
a video publication or scheduling from the Remotion generator.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Users can now take photos directly with the phone camera in the
Générateur Vidéo page, matching the behavior of Nouvelle Publication.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New POST /api/remotion/publish endpoint: uploads MP4 to Cloudinary,
creates post/media records, publishes or schedules per-page as a Reel
- Desktop and mobile pages show page selector + DateTimePicker + description
after video generation, matching the Reels publication workflow
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Video duration forced to 25-30s (content 22-27s + 3s ending)
- Logo watermark (bottom-right) visible throughout content section
- Ending slide: logo centered + store name with spring animation (3s)
- Text overlay now shows only the active spoken word (true TikTok sync)
- Server fetches logo from Cloudinary config and store name from user's pages
- Player preview also shows logo/storeName via /api/cloudinary/config + /api/pages
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>