174 lines
6.7 KiB
Markdown
174 lines
6.7 KiB
Markdown
> # ⛔ ABANDONNÉ — 2026-09-02
|
||
>
|
||
> **Ce plan n'est pas réalisable en l'état, et sa fonction est reprise ailleurs.**
|
||
>
|
||
> **Pourquoi.** Le lipsync décrit ici repose sur les *word timestamps* de **Google Cloud TTS**
|
||
> (`enable_time_pointing`, voir plus bas). Or le code livré tourne sur **Gemini TTS**
|
||
> (`GeminiTtsEngine`, `gemini-2.5-flash-preview-tts`, voix Sulafat / Umbriel) : **il n'existe aucune
|
||
> source de timestamps**. Le plan n'est pas « à faire », il est sans fondation.
|
||
>
|
||
> **Ce qui le remplace.** Le **portrait canon** d'un `Persona` (voir
|
||
> [studio-plan.md §3.8](studio-plan.md)) donne déjà un visage à afficher à côté du lecteur audio —
|
||
> l'essentiel de la valeur perçue, pour zéro travail supplémentaire. Si du mouvement est voulu plus
|
||
> tard, une **boucle vidéo de 6–8 s** (Kling 3.0 / Veo 3.1, lot 10 du plan Studio) est un meilleur
|
||
> produit qu'un lipsync 3 frames.
|
||
>
|
||
> **Ce qui reste vrai et a été récupéré dans `studio-plan.md`** : générer des vues d'un même
|
||
> personnage en réinjectant la première en référence. C'est exactement le mécanisme du canon en
|
||
> **jeu de vues** de `Persona` — ce plan l'avait trouvé avant, sur un cas particulier.
|
||
>
|
||
> Conservé pour l'historique de décision. **Ne pas implémenter.**
|
||
|
||
---
|
||
|
||
# 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)
|
||
|
||
```dart
|
||
// 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 :
|
||
|
||
```dart
|
||
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 |
|