Commit Graph
233 Commits
Author SHA1 Message Date
Claude 3af7f9418b fix(oauth): déduire l'URI de redirection du domaine public réel
Sans APP_URL, Facebook et TikTok recevaient
http://localhost:5555/api/{facebook,tiktok}/callback : une URI que les
fournisseurs refusent, donc aucune page connectable derrière un reverse
proxy. Le repli localhost n'est plus utilisé que hors requête HTTP ; en
service, la base publique est reconstruite depuis X-Forwarded-Proto /
X-Forwarded-Host (trust proxy est déjà activé).

- `resolvePublicBaseUrl(req)` centralise la résolution (APP_URL prioritaire)
- l'URI de redirection est déduite de la requête au démarrage du flux, à
  l'échange du code et dans l'affichage des paramètres : les trois restent
  identiques, comme l'exige OAuth
- docker-compose ne force plus APP_URL à localhost, qui rendait l'auto
  détection inopérante
- cookie de session en `secure: 'auto'` à défaut d'APP_URL, pour ne pas
  émettre un cookie non sécurisé derrière un proxy HTTPS

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Db2EazEcvnqgTTeg8oGTif
2026-09-11 18:01:28 +00:00
Claude 94e961d0f1 perf(api): alléger les requêtes du calendrier, des stats et de la médiathèque
Trois défauts se cumulaient : des réponses bien plus larges que nécessaire,
aucun index en base, et des boucles de requêtes là où une seule suffisait.

Réponses
- /api/scheduled-posts renvoyait la ligne `social_pages` entière, jetons
  d'accès Facebook et TikTok compris, jusque dans le navigateur. Seules les
  colonnes réellement affichées sont désormais sélectionnées.
- Le filtre de dates est poussé en SQL. Il était appliqué en JavaScript après
  avoir chargé tout l'historique — que le client ne bornait jamais. Le
  calendrier demande maintenant les six semaines affichées, le sélecteur de
  date les créneaux à venir, le bloc « publications récentes » ses 90 jours.
- /api/media accepte `limit` et `offset` (sans eux, réponse inchangée : des
  écrans doivent retrouver un média par son identifiant). La médiathèque
  pagine côté serveur au lieu de tout charger pour afficher cinq vignettes, et
  l'écran de création ne demande que les douze médias qu'il propose. Le total
  voyage dans l'en-tête X-Total-Count pour garder le compteur juste.

Index
- server/migrate.ts est le seul script exécuté au démarrage et n'en créait
  aucun ; ceux de migration-docker.sql ne servaient à rien, ce fichier n'étant
  monté nulle part. Postgres n'indexant pas les clés étrangères, le calendrier,
  l'historique et le scheduler parcouraient les tables entières. S'y ajoutent
  deux index partiels pour les boucles de fond : publications en attente et
  suivi asynchrone TikTok.

Requêtes
- /api/stats lisait quatre tables entières pour en compter les lignes : c'est
  désormais quatre COUNT filtrés.
- Les vues d'administration listaient les utilisateurs puis interrogeaient les
  pages de chacun ; une seule requête les remplace.
- getPostWithMedia et l'API externe chargeaient les médias un par un.
- /api/reels/ongoing chargeait tous les posts de l'utilisateur pour n'en garder
  que ceux en cours de génération, toutes les trois secondes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qns6Gcpd95bCHWUxySh4Ew
2026-09-10 06:39:16 +00:00
Claude d7f002a64f feat(facebook): renouvellement automatique des jetons de page
Un jeton de page collé à la main meurt au bout de 60 jours — ou du jour au
lendemain si le mot de passe change, si le compte perd son rôle d'admin ou si
Meta révoque l'autorisation — et on ne l'apprenait qu'à la première publication
en échec : le contrôle quotidien existant se contentait de marquer la page
« expired ».

On reprend la chaîne recommandée par Meta, comme dans PleinR : autorisation
OAuth, échange du code contre un jeton utilisateur longue durée (~60 j), puis
dérivation des jetons de page — qui n'expirent pas. Le jeton utilisateur est
conservé chiffré sur chaque page : c'est lui qui permet au TokenManager de
régénérer un jeton révoqué sans intervention humaine, et de se prolonger
lui-même avant échéance.

- facebook_config : App ID / App Secret de l'application, saisis dans les
  paramètres (chiffrés en base) ou par variables d'environnement.
- /api/facebook : configuration, connexion OAuth, callback qui crée ou met à
  jour les pages, contrôle d'une page à la demande, contrôle global.
- TokenManager : /debug_token pour connaître l'état réel du jeton, puis
  renouvellement automatique 15 jours avant l'échéance ou dès révocation ;
  repli sur le contrôle historique tant que l'application n'est pas configurée
  ou si Facebook est injoignable, pour ne pas passer l'UI au rouge sur une
  panne réseau. Le cron passe à deux fois par jour.
- Les comptes Instagram professionnels rattachés à une page reçoivent le même
  traitement, puisqu'ils publient avec le jeton de cette page.
- L'UI montre l'état constaté (jeton valide, bientôt expiré, révoqué), l'erreur
  à corriger et un bouton de contrôle immédiat ; un bandeau signale les pages à
  reconnecter.
- /api/pages ne renvoie plus aucun jeton au navigateur, seulement un indicateur
  de renouvellement automatique.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qns6Gcpd95bCHWUxySh4Ew
2026-09-10 06:11:43 +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 0570088722 feat(tiktok): servir le fichier de vérification du domaine
TikTok exige de prouver la propriété du domaine ("URL properties") avant
d'autoriser la Content Posting API, en servant un fichier à la racine du
site.

Le dossier public-root/ est exposé publiquement à la racine du domaine et
monté avant le catch-all du frontend. Y déposer un fichier suffit : pas de
modification de code ni de reconstruction du client, ce qui couvre aussi
les prochaines vérifications (autre domaine, Google, Meta).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018uUPZ1GNpEkYB1dpXHYPek
2026-08-11 16:27:26 +00:00
Claude 3e00a3ca67 feat(legal): préremplir les mentions légales de la société exploitante
Renseigne l'identité de FROUARD DISTRIBUTION (enseigne LA FOIR'FOUILLE)
comme valeur par défaut des pages /terms et /privacy : forme juridique,
capital, siège et numéro RCS. Les relecteurs TikTok ouvrent ces pages
pendant l'audit et refusent les mentions incomplètes.

Chaque champ reste surchargeable par variable d'environnement si l'outil
venait à être exploité par une autre société du groupe.

L'adresse de contact reste à fournir via LEGAL_CONTACT_EMAIL : elle ne
figure pas au registre, et le RGPD impose un point de contact pour
l'exercice des droits.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018uUPZ1GNpEkYB1dpXHYPek
2026-08-11 16:19:16 +00:00
Claude d0f63c68e4 feat(legal): servir les pages /terms et /privacy exigées par TikTok
L'audit de l'application développeur TikTok impose une URL publique pour
les conditions d'utilisation et pour la politique de confidentialité, et
les relecteurs les ouvrent réellement.

Les pages sont rendues en HTML côté serveur, sans authentification, pour
rester lisibles par un robot qui n'exécute pas le JavaScript du client.
La politique décrit précisément ce que l'intégration fait : données reçues
de TikTok (open_id, nom d'affichage, avatar, jetons), finalité de
publication, chiffrement des jetons au repos, suppression à la
déconnexion et marche à suivre pour retirer l'autorisation.

Les mentions de l'exploitant se configurent via LEGAL_COMPANY_NAME,
LEGAL_COMPANY_ADDRESS et LEGAL_CONTACT_EMAIL.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018uUPZ1GNpEkYB1dpXHYPek
2026-08-11 16:14:13 +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 8155b30c63 fix(openrouter): stop logging API key material, use a hash fingerprint
Address Codex review: the debug log previously included the key's
first 10 characters. Replace with a non-reversible SHA-256 fingerprint
so logs remain useful for correlating support reports without ever
exposing key material.
2026-07-14 06:31:18 +00:00
Claude de7fc7d03c fix(openrouter): trim API key and guard against empty key
An OpenRouter key with trailing whitespace/newline (common from
copy-paste) produces "Missing Authentication header" from their API,
which looked identical to a missing key. Trim apiKey on save (schema
level) and on use (defense in depth), fail fast with a clear message
if the stored key is empty, and log a masked key preview + model on
each generation call to make future auth failures diagnosable from
container logs.
2026-07-14 06:25:47 +00:00
Claude 09b4e597ae fix(openrouter): respect the requesting user's saved model config
generatePostText ignored the userId it received and always read an
arbitrary row via getAnyOpenrouterConfig() (LIMIT 1, no ORDER BY),
so a model chosen and saved in Settings could be shadowed by another
stale config row. Now it prefers the requesting user's own config,
falling back to the most recently updated shared one. Also swap the
retired anthropic/claude-3.5-sonnet default for a live OpenRouter slug.
2026-07-14 05:49:31 +00:00
Claude 07fd43a5ed Add GEMINI_API_KEY env var fallback for TTS
Add GEMINI_API_KEY to docker-compose so it can be set in Portainer.
Fall back to this env var when the DB app_config has no geminiApiKey,
so Gemini TTS works without requiring the user to save the key through
the Settings UI.

https://claude.ai/code/session_01QBvwHAMZzVYfWvy1U5paat
2026-05-19 13:39:37 +00:00
Claude e2002cf163 Pass Gemini API key in /reels/tts-preview route
The TTS preview route hardcoded the Gemini API key to undefined when
calling the ffmpeg service, causing the service to silently fall back to
Edge TTS even when the user selected Gemini. Mirror the lookup already
used in the reel processing route so the selected engine is honored.

https://claude.ai/code/session_01QBvwHAMZzVYfWvy1U5paat
2026-05-19 12:49:16 +00:00
Michael a9cda4ffd4 Fix sync-info route to use ttsEngine and ttsVoice
- 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')
2026-05-19 14:12:08 +02:00
Michael a2c2753b0b Add gemini_api_key column migration to migrate.ts 2026-05-19 12:25:14 +02: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 0209c3eacf feat(minimax): add Group ID support to fix rate limit quota
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
2026-05-18 13:50:41 +02:00
Michael cf20b0a264 fix(tts): surface TTS errors from Python to Node.js logs and DB
- 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
2026-05-18 13:26:13 +02:00
Michael 211b7dc272 fix(minimax): correct API payload and add TTS debug logging
- Fix model name: speech-2.8-hd -> speech-02-hd
- Fix bitrate: 128 -> 128000 bps
- Add channel, speed, pitch, vol to voice/audio settings
- Handle binary audio response (Content-Type: audio/*)
- Fallback audio field lookup: data.data.audio || data.audio
- Add background job log: ttsProvider + hasMinimaxKey
- Log full FFmpeg request body (text truncated, key masked)
2026-05-18 12:50:59 +02:00
Michael 35cf21209f fix(settings): prevent empty string API keys from being saved or preserved
- Add .min(1) validation to insertOpenrouterConfigSchema
- Strip empty/whitespace-only apiKey before fallback to existing key
- Return 400 if resulting apiKey is empty (forces user to enter valid key)
2026-05-18 12:32:11 +02:00
Michael b7ef4940f7 feat: add Minimax Speech as alternative TTS provider for Reels
- 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
2026-05-18 11:55:07 +02:00
MichaelandClaude Opus 4.7 9932561050 feat: add list, edit, delete endpoints to external API
GET /api/v1/posts - list upcoming scheduled posts with filters
PATCH /api/v1/posts/:id - edit content, schedule, or image
DELETE /api/v1/posts/:id - remove scheduled post and cascading relations

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 22:05:20 +02:00
MichaelandClaude Sonnet 4.6 391acfdb83 feat: add external API for publishing posts with image URL and scheduling
- New POST /api/v1/publish endpoint: download image from URL, create post and schedule it per page
- New GET /api/v1/pages endpoint: list available pages for external callers
- API key auth via X-API-Key header, configurable from admin settings (stored in DB)
- New app_config table (migration included) to store the external API key
- Admin-only settings section (desktop + mobile) to set/revoke the key
- Fallback to EXTERNAL_API_KEY env var if no DB key is configured

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 13:12:27 +02:00
MichaelandClaude Opus 4.7 d763be6322 fix: prevent ENOENT on scheduled reel publishing when local file deleted early
- 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>
2026-05-04 14:08:15 +02:00
MichaelandClaude Opus 4.7 37aba33425 fix: precise TTS word-boundary timing and remove wasted sync calculations
- 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>
2026-05-04 13:13:55 +02:00
MichaelandClaude Opus 4.7 9ce735dc23 feat: add adaptive TTS text-to-speech synchronization for Reels with real-time sync info widget
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>
2026-05-04 12:35:04 +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 73e3a9d30f feat: add remotion router for video rendering with automated TTS synchronization and asset embedding 2026-04-29 10:33:05 +02:00
Michael f56a89e61c feat: implement FacebookService to handle post publishing for text, photos, videos, and multi-photo carousels 2026-04-01 20:19:48 +02:00
Michael 3122e162cc feat: implement user authentication and management routes with multer file upload support 2026-03-30 11:30:56 +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
Michael e82d73c4e1 feat: Add FFmpeg service for video processing, TTS generation, and dynamic subtitles. 2026-03-27 09:34:16 +01:00
MichaelandClaude Sonnet 4.6 2a93c2aea7 fix: detect publish_video permission error + show required permissions in Pages UI
- Detect Facebook error subcode 1363042 (missing publish_video permission)
  and throw a clear actionable message explaining how to fix it
- Add permission notice banner in Pages admin showing the 3 required
  Facebook permissions: pages_manage_posts, pages_read_engagement, publish_video

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 20:51:47 +01:00
MichaelandClaude Sonnet 4.6 8f13d3021e fix: use multipart/form-data for all resumable upload phases + verbose logging
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>
2026-03-25 20:46:35 +01:00
MichaelandClaude Sonnet 4.6 47ed1711dd fix: use Facebook Resumable Upload API for video publishing
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>
2026-03-25 20:15:11 +01:00
MichaelandClaude Sonnet 4.6 5d7ab93bf8 feat: add Ken Burns effect, fix caption visibility and Facebook binary upload
- 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>
2026-03-25 17:01:52 +01:00
Michael 487a8ee352 feat: Add Remotion video rendering API and composition for dynamic video generation from images, text, and audio. 2026-03-25 16:52:58 +01:00
Michael 56210687a2 feat: implement Facebook posting service, add new sidebar component, and introduce Remotion server route. 2026-03-25 16:36:57 +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 3d51fdc90b feat: implement Remotion video rendering endpoint with image, audio, and TTS capabilities. 2026-03-25 13:07:17 +01:00
Michael 0c525665d8 feat: implement Remotion image composition with dynamic text, audio, and branding, and a server-side rendering API. 2026-03-25 12:53:52 +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 0f49218f62 fix: embed local images as base64 data URLs for Remotion renderer
Remotion's headless Chromium cannot reliably fetch http://localhost
inside Docker due to network sandboxing. Instead of HTTP URLs, convert
all local /uploads/... files to data:image/...;base64,... before passing
to inputProps. This is reliable in all environments (Docker, dev, CI).

Audio files remain as HTTP URLs since they are loaded via Web Audio API
which has different network handling.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 11:26:04 +01:00
MichaelandClaude Sonnet 4.6 a40a5a500e fix: read logo file from disk before uploading (multer uses diskStorage)
req.file.buffer is undefined with diskStorage — must read the temp file
from req.file.path using fs.readFileSync instead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 11:14:17 +01:00
MichaelandClaude Sonnet 4.6 9681c62ae8 fix: resolve logo URL to absolute before passing to Remotion renderer
Logo stored as relative /uploads/logos/... was resolving to Remotion's
bundle server (port 3000) instead of the app (port 5555).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 11:12:59 +01:00
MichaelandClaude Sonnet 4.6 a638cfba6a fix: use localhost for Remotion image URLs instead of Docker service hostname
Remotion's headless Chromium runs inside the same container as the app,
so it must use http://localhost:PORT to fetch local media files.
The Docker service hostname (socialflow-app) is only reachable from
other containers on the Docker network, not from within the container itself.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 11:07:07 +01:00
MichaelandClaude Sonnet 4.6 abaee384bf fix: resolve relative media URLs to absolute before passing to Remotion renderer
Remotion's headless Chromium cannot load relative /uploads/... paths.
Library images were passed as-is, causing "Error loading image" at render time.
Now resolves them using INTERNAL_APP_URL (Docker) or the request host (dev).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 11:00:30 +01:00