Commit Graph
24 Commits
Author SHA1 Message Date
Claude 1d47763b29 feat(reels): interrupteur « Afficher le logo sur la vidéo »
Le petit logo affiché pendant la vidéo devient optionnel (activé par
défaut), sur les 4 pages Reel (vidéo et images, bureau et mobile) ainsi que
dans l'aperçu. Le grand logo de l'effet de fin reste piloté par son propre
interrupteur. Pris en charge par le rendu Remotion et par le rendu FFmpeg de
secours (show_watermark).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Ze4bs7tpF1KGWUk6ZZSZ4
2026-09-28 06:45:38 +00:00
Claude 9b66a6dc5a feat(reels): montage Remotion unifié, aperçu en direct, tests et CI
Montage (étape 3) :
- Nouvelle composition ReelVideo : sous-titres animés mot à mot (3 styles :
  Impact, Surligné, Épuré), logo, effet de fin et fondu, en React
- Le service Python prépare l'image (recadrage, HDR, stabilisation, dernière
  image figée) et la piste son finale (/prepare-reel) ; Remotion compose
- Reels d'images sur les mêmes composants, avec le vrai minutage de la voix
- Police Montserrat embarquée (plus de dépendance à Google Fonts)
- REEL_RENDERER=ffmpeg conserve le rendu FFmpeg, plus rapide, en secours
- Script de pré-bundle réparé (échouait en silence : require en ESM)

Interface (étape 4) :
- Aperçu en direct avec @remotion/player, identique au rendu final ; la voix
  testée cale les sous-titres, sinon minutage estimé
- Choix du style de sous-titres sur les 4 pages Reel
- Vraie progression : étape réelle du rendu, échecs visibles 24 h avec leur
  cause ; fin de la barre simulée et du faux « publié avec succès »

Outillage (étape 5) :
- Tests vitest (minutage identique à Python, validation, sécurité) et
  pytest ; CI GitHub Actions (tsc, tests, build, ruff)
- Captures d'écran, out.mp4 et scripts de test retirés de la racine

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Ze4bs7tpF1KGWUk6ZZSZ4
2026-09-24 14:37:42 +00:00
Claude 3c6a66bf64 feat(reels): voix Gemini complète, sous-titres mot à mot et rendu de qualité
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
2026-09-24 12:31:27 +00:00
Claude d24ab0d4b7 feat(reels): file d'attente persistante des rendus (reel_jobs)
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
2026-09-24 09:31:31 +00:00
Claude ac8b494f24 fix: vignettes vidéo persistantes et remise à zéro des erreurs de types
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
2026-08-12 08:00:48 +00:00
Claude c69fe4e462 fix(ia): afficher la cause réelle d'un échec de génération de texte
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
2026-08-12 07:49:23 +00:00
Claude b95cd23ebc feat(tiktok): publier les reels sur les comptes TikTok des magasins
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
2026-08-11 14:29:38 +00:00
Claude 2188fa458d fix: recover gracefully from stale sessions causing repeated 401s
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.
2026-07-27 11:56:56 +00:00
Claude 50a011ce23 Simplify Gemini voice options and default to male voice (Charon)
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
2026-05-19 13:19:15 +00:00
Michael 72b13b3e42 Add Google Gemini TTS as alternative to Edge TTS
- 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
2026-05-19 12:17:42 +02:00
Michael 59bc56aedc 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
2026-05-19 10:55:01 +02:00
MichaelandClaude Sonnet 4.6 797e12dde5 refactor(tts): replace Minimax+Freesound with Piper TTS
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>
2026-05-19 09:15:26 +02:00
Michael 9913c307ed feat: implement Remotion video generation, text-to-speech, and automated social media publishing workflow 2026-04-29 11:09:28 +02:00
Michael 0eca57efab feat: Introduce Remotion video creation and publishing page with a media thumbnail component and backend routes. 2026-03-27 09:45:38 +01:00
MichaelandClaude Sonnet 4.6 b8dc93eb2d fix: invalidate scheduled-posts cache after video publish/schedule
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>
2026-03-25 17:11:58 +01:00
Michael e1a6d1e06c feat: add mobile Remotion video generator with image, text, audio, and Facebook publishing features. 2026-03-25 16:16:30 +01:00
Michael 59caa19fbe feat: add mobile video generator page with Remotion integration for rendering and publishing. 2026-03-25 11:46:15 +01:00
MichaelandClaude Sonnet 4.6 281597e944 feat: add camera capture button to mobile video generator
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>
2026-03-25 10:56:33 +01:00
Michael 8f7f580b2f feat: Implement Facebook Reels API, including Jamendo music management, AI text generation, and MinIO storage integration. 2026-03-25 08:14:17 +01:00
MichaelandClaude Sonnet 4.6 ff5e02e30d feat: clean up mobile remotion page — remove preview player and download button
Remove unused Player, ImageComposition, useMemo imports and all associated
computed values (combinedUrls, previewWordTimings, ENDING_FRAMES, totalFrames,
previewLogoUrl, previewStoreName, cloudinaryConfig query). Remove Aperçu card
with broken Player preview, download button, and conditional generate button wrapper.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 20:27:55 +01:00
MichaelandClaude Sonnet 4.6 4072f8954f feat: add publication scheduling to Remotion video generator
- 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>
2026-03-24 19:36:53 +01:00
MichaelandClaude Sonnet 4.6 9145bc5941 feat: add logo watermark, ending slide, 25-30s duration, and word-sync text
- 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>
2026-03-24 19:13:07 +01:00
Michael 24a1d39e88 feat: Implement Remotion video generation feature, allowing users to combine images with text overlay and text-to-speech. 2026-03-24 16:44:06 +01:00
Michael 526e608077 feat: Implement Remotion video generation with client-side image selection and server-side rendering. 2026-03-24 12:48:53 +01:00