DOCS/v2/talking-head-plan.md
Thomas Fransolet a5a8ecdb20 Documentation interne MyInfoMate / Unov
Import initial de la documentation : statut, roadmap, plans V1/V2,
specs verticales (creche, sport), audits securite, plan de test,
analyse concurrentielle et maquettes de design.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 11:17:01 +02:00

5.4 KiB
Raw Blame History

Avatar Parlant (Talking Head) — Plan d'intégration

Contexte : Delta technique #6 issu de myinfomate-ai-persona-analysis.md. Feature V2 — app mobile et borne uniquement. Hors scope smart glasses (pas d'écran).


Concept

Portrait 2D animé par lipsync synchronisé au TTS pré-généré ou dynamique. 3 frames PNG par persona, animées côté Flutter selon les timestamps retournés par Google Cloud TTS.


Assets par persona

Frame État bouche Usage
frame_0.png Fermée Idle, fin de phrase, consonnes labiales (M, B, P)
frame_1.png Mi-ouverte Consonnes courantes, transition
frame_2.png Ouverte Voyelles (A, E, I, O, U)

Modèle de génération — À tester

Deux candidats dans l'écosystème Google (même billing GCP que Gemini et Cloud TTS) :

Modèle Prix/image 3 frames Notes
Gemini 2.5 Flash Image $0.039 ~$0.12 Bon rapport qualité/prix
Gemini 3 Pro Image $0.134 ~$0.40 Meilleure qualité, meilleure consistance de style

Test à faire : générer les 3 frames d'un même personnage avec chaque modèle et comparer la cohérence de style entre les frames (critère numéro 1 pour un lipsync crédible). Si Gemini 3 Pro est significativement plus consistant, le surcoût de $0.28 par persona est négligeable.

Par défaut dans le code : Gemini 2.5 Flash Image, configurable via constante pour switcher facilement.

Prompts de génération

Trois appels séquentiels — les prompts 2 et 3 doivent envoyer l'image de référence (frame 0) pour garantir la cohérence visuelle.

Prompt 1 — Base (bouche fermée)

2D cartoon illustration of a friendly [description du persona],
warm smile, mouth closed, facing slightly to the right,
bust portrait, clean white background, consistent art style,
flat colors, soft shading. Reference sheet style.

Prompt 2 — Mi-ouverte (+ image de référence)

Same character as the reference image, exact same art style,
same colors, same angle, same lighting,
mouth slightly open as if mid-speech.

Prompt 3 — Ouverte (+ image de référence)

Same character as the reference image, exact same art style,
same colors, same angle, same lighting,
mouth open as if speaking a vowel sound.

Coût

  • Gemini 2.5 Flash : ~$0.12 par persona (one-shot)
  • Gemini 3 Pro : ~$0.40 par persona (one-shot)

Stockage Firebase

{instanceId}/personas/{wakewordId}/frame_0.png   ← bouche fermée
{instanceId}/personas/{wakewordId}/frame_1.png   ← mi-ouverte
{instanceId}/personas/{wakewordId}/frame_2.png   ← ouverte

Les URLs sont stockées dans la config du persona (aux côtés de VoiceName et PersonaPrompt).


UX dans le CMS (manager-app)

Dans la page "Configuration du guide IA", section par persona :

  1. Champ PersonaPrompt (description du personnage : "chevalier médiéval jovial")
  2. Bouton "Générer l'avatar" → appel backend → DALL-E 3 × 3 → upload Firebase → prévisualisation des 3 frames
  3. Possibilité de re-générer (coût ~$0.12 à chaque fois, à afficher clairement)
  4. Prévisualisation : animation en loop des 3 frames dans le CMS pour validation

Lipsync Flutter

Mode simple — loop (V2 MVP)

// Pendant la lecture audio : boucle les 3 frames
Timer.periodic(Duration(milliseconds: 90), (timer) {
  if (!isPlaying) { setState(() => currentFrame = 0); timer.cancel(); return; }
  setState(() => currentFrame = (currentFrame + 1) % 3);
});

~30 lignes, zéro dépendance. Suffisant pour une première version convaincante.

Mode avancé — word timestamps (V2+)

Google Cloud TTS avec enable_time_pointing: SSML_MARK (ou WORD) retourne des timestamps par mot.

Mapping côté Flutter :

String getFirstSignificantChar(String word) => word.toUpperCase().trimLeft()[0];

int frameForWord(String word) {
  final c = getFirstSignificantChar(word);
  if ('MBP'.contains(c)) return 0;           // bouche fermée
  if ('AEIOU'.contains(c)) return 2;         // bouche ouverte
  return 1;                                   // mi-ouverte
}

À chaque timestamp : setState(() => currentFrame = frameForWord(word)).

Note : enable_time_pointing retourne des timestamps au niveau du mot, pas du phonème. Le mapping par première lettre est une approximation raisonnable — suffisant visuellement à la vitesse de lecture normale.


Intégration avec le TTS pré-généré

Pour le contenu pré-généré (articles, POIs), les timestamps sont générés en même temps que le MP3 et stockés à côté :

{instanceId}/tts/article/{sectionId}/{wakewordId}/{lang}.mp3
{instanceId}/tts/article/{sectionId}/{wakewordId}/{lang}.json   ← timestamps

Le JSON contient la liste [{word, startTime, endTime}] — Flutter le charge une fois et l'utilise pour l'animation.

Pour les questions libres (TTS dynamique), les timestamps sont retournés en même temps que l'audio dans la réponse de l'endpoint /ask.


Dépendances avec les autres deltas

Delta Lien
#2 — Persona config PersonaConfig stocke les URLs des 3 frames + le PersonaPrompt utilisé pour DALL-E
#4 — TTS pré-généré Génère le MP3 + timestamps en même temps ; même job Hangfire
#3 — Endpoint /ask Retourne audio + timestamps pour questions libres
#5 — Canal Flutter Intègre le widget Talking Head ; smart glasses = hors scope