Formats

05 — Le format motion : la vidéo avec emaki

Statut : fait · Phase : P4 · Mis à jour : 25/10/2026.

Source : the emaki repository. Ce document reprend emaki/PLAN.md et répond à ses questions ouvertes, puisque le projet hôte est maintenant Récit.

0. Ce qu'apporte emaki

  • 10 packages @emaki/* en 0.1.0-beta.7 (studio est privé) :

    PackageRôle
    schemaDeck zod 4, parseDeck (seule source de vérité), ops
    coremoteur et timing
    uiprimitives
    blockstitle, chapter, statement, stat, list, compare-bars, ui-mock, ui-scene (framer-motion 12)
    themeswarm-editorial, saas-product
    renderRemotion 4 vers MP4
    extractle handover, format souple sans id
    mcpserveur MCP
    clicommande emaki
    studioéditeur Monaco
  • Formats d'image : 16:9, 1:1, 9:16.

  • Garde-fous : goldens PNG (fixtures/goldens/saas-*.png dans emaki, aujourd'hui packages/formats/motion-render/fixtures/goldens/, pnpm -F @recit/motion-render goldens:check), tests vitest et règle « aucune métrique inventée ».

  • « Deux chemins de rendu qui correspondent » : le même moteur de blocs joue dans le navigateur (prévisualisation, studio) et dans Remotion (MP4). C'est ce qui permettra d'intégrer une vidéo jouable dans une page du framework de sites sans Remotion.

1. Réponses aux questions ouvertes de emaki/PLAN.md

QuestionRéponse
Projet hôte ?Récit (ce fork) : pnpm 11, Node ≥ 22.22, nx, React 19.2, zod 4.6, TS 6, Vite 8
Mode d'intégration (phase 1)Copie, comme presentation-engine (04-migration.md) : le .git local d'emaki a été supprimé. Référence : dernier commit 1b86ccd (0.1.0-beta.7), cité dans chaque commit d'import
Emplacement et scopecopie directe dans les dossiers finaux (packages/formats/motion*, packages/mcp), sans dossier emaki/ intermédiaire. Le scope devient @recit/* (voir le tableau de 03-packaging.md)
Emaki reste-t-il publiable seul ?Non : les packages deviennent @recit/motion*. Les @emaki/*@beta déjà publiés reçoivent un npm deprecate qui pointe vers @recit/motion
Markdown par défaut ?Oui, dans Récit. Le Markdown est la porte d'entrée commune, et le JSON reste supporté (deck.json) comme format machine et MCP
Format hybride, doc vers film ?Oui, c'est la promesse de Récit : un même story.md se rend en deck (recit build) et en vidéo (recit render). Une page MDX de la doc peut intégrer l'un ou l'autre (<StoryEmbed slug format="motion" />)

2. Phases

Les phases V2 à V4 peuvent démarrer dans le dépôt emaki, avant l'import, comme le prévoyait emaki/PLAN.md. Il faut juste écrire la spec Markdown dans la syntaxe Récit (V3).

V1 — Import dans le fork (S/M), reprend les phases 1 et 2 d'emaki

Fait le 24/10/2026 (journal.md §2) : préparé dans une copie du dépôt, puis rapatrié. Dépôt réuni vérifié : pnpm check, build et typecheck de 23 projets, 1 938 tests, 16 goldens, 139 e2e.

  • Dans emaki (sans git) : vérifier pnpm build && pnpm test && pnpm goldens:check avant la copie.
  • Copier chaque package dans son dossier final (packages/formats/{motion,motion-blocks,motion-render}, packages/mcp). L'origine (emaki@1b86ccd, l'auteur) est dans NOTICE.md, le README.md et l'en-tête de src/index.ts de chaque package ; les commits sont faits par l'utilisateur. extract est fusionné dans @recit/motion ; le studio n'est pas importé (squelette packages/tools/studio).
  • Ne pas copier l'outillage d'emaki (turbo.json, tsconfig.base.json, pnpm-workspace.yaml, .changeset/, lockfile) : nx, la base TS et le catalogue du fork le remplacent.
  • allowBuilds : esbuild: true suffit pour Remotion, rien d'autre n'a été ajouté.
  • Conserver la condition d'export development. Elle devient la convention de tous les @recit/* (voir 03-packaging.md).
  • Dépendances :
    • React 19.2.8 et zod 4.6.5 viennent du catalogue ;
    • react >=18 passe à >=19.2.7 ;
    • framer-motion est remplacé par motion (motion/react) dès l'import : une seule stack ;
    • Remotion 4 et le SDK MCP restent locaux à motion-render et mcp ;
    • vérifier minimumReleaseAge.
    • Reste ouvert : Remotion impose une seconde version de zod (4.4.3) dans l'arbre de motion-render (@remotion/studio). Une autre copie existait par le pair optionnel site >=0.93.0 de @recit/site-stories, résolu sur la copie npm du framework de sites au lieu du fork (antérieur à P4). Relier le fork casse le typecheck de @recit/site-stories (les sources du fork ne passent pas la config TypeScript de Récit) : à traiter avec TE-3 de audit.md. Le 25/10, après pnpm install, zod 4.4.3 n'est plus tiré que par Remotion.
  • Chrome Headless Shell (environ 93 Mo) : mettre en cache en CI (~/.cache/remotion), ou sauter le rendu MP4 en CI et ne garder que les goldens PNG. Reporté à V2 (pas encore de job motion en CI).
  • .gitignore : out/ (rendus MP4 de recit render).
  • Goldens : scripts goldens et goldens:check de @recit/motion-render (pnpm -F @recit/motion-render goldens:check), plutôt que des scripts racine. Les 16 PNG ont été régénérés après le passage des fixtures showcase-* en Lorem Ipsum. Ils dépendent des polices installées sur la machine (tolérance de 0,2 % pour le lissage) : à figer dans un conteneur en CI (§4).
  • AGENTS.md d'emaki : généré dans packages/formats/motion/AGENTS.md, pas à la racine (qui reste le guide du framework de sites). Reste à le référencer depuis context.md.
  • MCP : claude mcp add recit -- npx recit mcp serve (fusion du CLI, V6). Le binaire recit-mcp de @recit/mcp reste disponible.

Critère de sortie : depuis la racine du fork, nx run-many -t build test -p '@recit/motion*' @recit/mcp et goldens:check passent (atteint le 24/10). recit render fixtures/hello.deck.json produit le même MP4 que emaki render avant l'import : reporté à la fusion du CLI d'emaki (V6), mis de côté dans scratch-emaki-cli/.

V2 — Un seul chargeur de deck (S), reprend la phase 3

Fait : @recit/motion/source (formatOf, parseDeckSource) et @recit/motion/node (readDeckFile, writeDeckFile), utilisés par motion-render (loadDeck, script des goldens) et mcp (apply_ops, build_deck, extract). Le CLI et le studio suivront en V6.

Sans changement par rapport à emaki/PLAN.md :

  • formatOf, parseDeckSource, readDeckFile et writeDeckFile remplacent les 5 JSON.parse(readFileSync(…)) (cli, mcp readDeck, render loadDeck, studio store.ts) et les 4 writeFileSync(JSON.stringify(…)).
  • Les tests existants passent sans modification.

V3 — Spec Markdown du format motion (S), reprend la phase 4 avec la syntaxe Récit

C'est le changement principal par rapport au plan emaki : pas de seconde syntaxe. Un récit format: motion s'écrit comme un deck :

MarkdownMarkdown
--- title: Hello, Récit format: motion aspectRatio: 9:16 # alias accepté : aspect motion: theme: warm-editorial wordsPerSecond: 2.6 --- --- id=open Lorem # Ipsum dolor sit amet. --- id=why Lorem ipsum. **Dolor sit amet.** :::notes Voice-over: lorem ipsum dolor sit amet. ::: --- type=stat id=proof hold=1 # 30s vertical film _rendered locally_
  • Les attributs de scène sur le séparateur (type, id, hold, dur, unit, number, kicker…) remplacent <!-- type: …, id: … -->. Ce commentaire reste accepté comme alias (voir 02-architecture.md, section 3).
  • Le tableau d'inférence est celui d'emaki, inchangé (title, chapter, statement, list, compare-bars, stat jamais inféré, ui-* en yaml, échappatoire ```yaml / ```json). Les équivalences avec les layouts de deck sont dans 02-architecture.md, section 4.
  • Pas d'inférence ambiguë : une erreur avec numéro de ligne (story.md:14 (stat): label is required).
  • Rédiger la spec dans sites/docs/pages/formats/motion.mdx (en anglais).
  • La valider sur hello, saas, showcase-saas et showcase-warm avant de coder (packages/formats/motion/fixtures/*.story.md).

V4 — Parseur Markdown → Deck (M), reprend la phase 5

Au lieu d'un @emaki/markdown qui reparse avec son propre remark, on réutilise @recit/core :

Code
story.md ─► compileStory (scènes, attrs, mdast, source.line) ─► storyToHandover ─► extractHandover ─► parseDeck ─► Deck
  • storyToHandover(story) dans @recit/motion. Il lit scene.mdast et scene.attrs, applique le tableau d'inférence et produit un sourceMap (scène → ligne) à partir de scene.source.
  • parseMarkdownDeck(text) remappe HandoverIssue.scene vers des lignes Markdown.
  • Le brancher dans parseDeckSource(text, "md") (V2).
  • Le lint texte (glyphes non couverts par les polices du thème) s'applique aussi au .md.
  • :::notes → champ voiceover (ou caption) du schéma Deck. C'est un ajout sans rupture, en version: 1.

Tests :

  • Équivalence : hello.story.md, saas.story.md et showcase-*.story.md donnent un Deck égal (toEqual) à leur .deck.json.
  • Goldens : les PNG rendus depuis le .md sont identiques aux goldens actuels.
  • Erreurs : un cas par règle d'inférence, avec le bon numéro de ligne.

V5 — Sérialiseur Deck → Markdown et réécriture (M/L), reprend la phase 6

  • deckToMarkdown(deck) : sortie canonique dans la syntaxe Récit (attributs de séparateur, id= toujours émis, bloc yaml pour les props sans forme naturelle).
  • Propriété testée : parse(deckToMarkdown(deck)) est égal à deck pour toutes les fixtures.
  • Réécriture : v1 canonique, documentée (faite : writeDeckFile sur un .md). v2 chirurgicale via scene.source : seules les scènes touchées par replace, patch, insertAfter et remove sont réécrites, le reste est conservé à l'octet près.
  • CLI : recit convert deck.json --to md et l'inverse, recit init --format motion.

V6 — Intégration à Récit : CLI, site, MCP, studio (M), reprend la phase 7

CLI (@recit/cli) :

  • recit render story.md [--aspect 9:16] [--output out/] charge @recit/motion-render à la demande. Si le package manque, le message d'erreur explique quoi installer.
  • recit validate, recit dur (durée totale) et recit extract reprennent les commandes emaki.
  • recit dev story.md : si format: motion, prévisualisation dans le navigateur avec le moteur de blocs et le timing, live reload. Le lecteur est celui de @recit/motion-blocks (MotionFrame, filmTimeline) : recit dev, recit dur, la page et le MP4 coupent sur les mêmes images. recit render lit recit.config comme validate et dur.

Site et le framework de sites (@recit/site-stories) :

  • <StoryEmbed slug="hello" format="motion" /> joue la vidéo dans la page avec le moteur navigateur de @recit/motion-blocks, sans Remotion ni MP4. Un MP4 prérendu peut être fourni en poster/src pour le SSR. Reste : pas de job CI qui rend les MP4 de /video, et pas de redirection video.recit.dev configurée.
  • Route /present/<slug> : lecteur plein écran pour un récit motion.
  • Onglet Formats › Motion dans sites/docs et site sites/motion (motion.recit.dev, video.recit.dev en alias, voir 07-domaines.md) : spec (V3), galerie d'exemples, aspects 16:9, 1:1 et 9:16.
  • CSS des blocs : même stratégie que pour le deck, limitée au wrapper avec l'ordre des layers.

MCP (@recit/mcp) :

  • Tous les outils deckPath acceptent .md (fait en V2 : apply_ops et render lisent par readDeckFile, out en .md écrit du Markdown).
  • build_deck accepte markdown. Ajouter l'outil describe_format(format) (JSON Schema et spec Markdown, 09-langues-et-json.md §7). La version du serveur MCP est lue dans son package.json.
  • Le guide emaki://guide devient recit://guide/motion. Ajouter recit://guide/deck pour les decks.

Studio (@recit/studio, privé ; squelette, import du studio d'emaki différé tant qu'il est simulé, journal.md §5) :

  • Ouverture et hot-reload d'un .md, Monaco en mode markdown, erreurs avec numéro de ligne.

V7 — Renommage et unification (M)

  • Thèmes : exprimer warm-editorial et saas-product comme thèmes @recit/theme (tokens HSL partagés). Un récit garde le même thème en deck et en vidéo. Fait : RECIT_THEMES de @recit/theme (tokens HSL, polices, palette de données, échelle typographique motion), toMotionTheme dans @recit/theme/motion, @recit/motion-blocks n'a plus de copie. Le sélecteur est theme: <nom> (front matter) ou theme.name (recit.config) ; le film le lit après motion.theme. Goldens inchangés (0,000 %). Doc : docs.recit.dev/theme/named-themes.
  • npm deprecate "@emaki/*" "moved to @recit/motion" : à faire par l'auteur d'emaki (mainteneur npm yathavan), pas par Récit. Nous n'avons pas les droits sur le scope @emaki et Récit ne publie rien sur npm avant P5.

3. Ordre

Code
V2 ─► V3 ─► V4 (possibles dans le dépôt emaki, avant l'import) V1 (import) ─► V4 rebasé sur @recit/core ─► V5 ─► V6 ─► V7

V4 dépend de @recit/core avec scene.mdast, scene.attrs et scene.source, donc de la phase 1 de la roadmap (import de presentation-engine). Si on commence dans emaki, un split et un remark minimal font office de bouchon, remplacé ensuite par compileStory.

4. Risques propres à la vidéo

RisqueParade
Remotion et Chrome alourdissent tout le monorepo@recit/motion-render isolé, dépendance optionnelle de la CLI, rendu MP4 hors CI par défaut
Licence Remotion (licence d'entreprise au-delà d'une certaine taille d'équipe)la vérifier avant publication et la documenter dans NOTICE.md. Le chemin navigateur n'en dépend pas
Deux syntaxes Markdown qui divergentune seule : celle de Récit. Le commentaire HTML n'est qu'un alias
Un layout de deck sans équivalent vidéoerreur explicite en v1, puis rendu en image fixe (capture de la slide) en v2
framer-motion contre motion en double dans le bundlemotion seul dès l'import (V1), size-limit sur @recit/site-stories
Goldens instables entre macOS et la CI Linuxgoldens générés en CI (conteneur fixe), tolérance de diff documentée