Communitygithub.com

ci-story

Génère des vidéos d'histoires illustrées et narrées, spécialement conçues pour les débutants en chinois mandarin, avec une forte répétition lexicale et un input compréhensible.

Qu'est-ce que ci-story ?

ci-story est un outil avancé conçu pour créer des vidéos pédagogiques d'histoires courtes en chinois mandarin, adaptées aux débutants complets. Chaque leçon prend la forme d'une vidéo animée où des personnages simples interprètent une histoire, tandis qu'une voix calme narre des phrases courtes, répétées de nombreuses fois pour faciliter la compréhension et la mémorisation. Les sous-titres dans l'écriture chinoise et la romanisation (pinyin) mettent en évidence le mot prononcé, et la dernière partie de la vidéo (environ 15%) supprime les sous-titres pour un entraînement à l'écoute pure. L'outil s'intègre parfaitement aux workflows de Claude et Codex : il planifie les leçons en fonction du programme du cours, des besoins de l'apprenant (mots difficiles ou connus) et des révisions dues ; génère les illustrations (SVG avec Claude, PNG généré avec Codex) ; produit la narration via un moteur TTS local (Qwen) ou en ligne (Edge) ; et compose la chorégraphie des personnages et objets dans une vidéo finale. L'accent est mis sur un vocabulaire concret et montrable, avec un minimum de 10 occurrences par mot nouveau, et une structure narrative en trois temps (introduction, contraste, action/récapitulation). L'utilisateur doit valider le plan avant toute génération, ce qui garantit un contrôle créatif et un contenu parfaitement adapté.

Compatible avec✓Claude Code✓Codex CLI~Cursor
npx skills add https://github.com/victorblaga/ci-story/tree/HEAD/skills/ci-story

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Que fait ci-story ?

Chaque leçon est une courte vidéo. Un petit groupe de personnages interprète une histoire simple tandis qu'une voix calme narre des phrases courtes, et chaque phrase se répète de nombreuses fois, l'image rendant son sens évident. Les sous-titres dans l'écriture de la langue ainsi que la romanisation mettent en évidence le mot prononcé. Les 15 % environ de la fin se déroulent sans sous-titres, pour une écoute pure.

Élaborez vous-même le plan à partir des données du cours et de la demande de l'apprenant, puis présentez-le et attendez son accord avant de générer quoi que ce soit (étape 5). Hormis cela, ne posez pas de questions en cours d'exécution, sauf lors de la configuration initiale et avant des téléchargements importants.

Emplacements

  • <skill> : le dossier de cette compétence (celui contenant ce fichier), avec scripts/, references/ et assets/.

  • Configuration : ~/.config/ci-story/config.json contient workspace, tts_project (uniquement pour une voix qwen locale) et env en option. S'il est manquant ou si un chemin qu'il contient n'existe pas, suivez d'abord references/setup.md.

  • Un cours : <workspace>/courses/<lang>/, contenant :

    • course.json : voix, polices
    • notes.md : préférences de l'apprenant
    • art/drawn/, art/generated/
    • audio-cache/
    • lessons/NNN-slug/

    Lorsque l'apprenant ne nomme pas de cours et qu'un seul existe, utilisez celui-ci.

  • N'exécutez jamais uv lock --upgrade dans le projet tts, et ne modifiez jamais la source d'un chemin torch. Une roue torch de plusieurs Go peut être épinglée sous forme de fichier local.

Workflow pour « une nouvelle histoire »

  1. Lisez le cours. Exécutez python3 <skill>/scripts/curriculum.py report <course>. Lisez notes.md, ainsi que le ou les deux derniers fichiers lesson.json et leur story.py, pour la continuité du casting, des accessoires et des aides que vous pouvez réutiliser.

  2. Enregistrez ce que l'apprenant a dit.

    • Un mot difficile ou oublié : ajoutez une ligne struggling: 给.
    • Un mot qu'il connaît bien : ajoutez solid: 苹果.
    • Des mots dont il veut plus : ajoutez focus: ….

    Les lignes de balise ne contiennent que des mots, séparés par des virgules. Une ligne ultérieure remplace une ligne antérieure pour le même mot, et les remarques entre (parenthèses) sont ignorées.

  3. Planifiez, comme un enseignant :

    • Honorez d'abord la demande : son sujet, ses mots d'intérêt ou son objectif de révision.
    • Choisissez 2–3 nouveaux mots. Chacun doit être concret et montrable (un objet, une action visible, un nombre, une couleur ou un contraste comme a/n'a pas ou grand/petit), courant dans le langage quotidien et capable de se combiner avec des mots connus en phrases simples. La première leçon d'un cours n'utilise que 5–7 mots au total (par exemple 这 是 一个 plus 2–3 noms), chacun entendu 10+ fois. Puisque chaque mot d'une première leçon est nouveau, listez-les tous dans new_words.
    • Intégrez chaque mot dû au moins 3 fois. Utilisez les mots fragiles et struggling plus souvent. Laissez de côté les mots solid sauf si l'histoire en a besoin.
    • Chaque nouveau mot est entendu au moins 10 fois, chaque fois avec l'image montrant ce qu'il signifie.
    • Structure de l'histoire : introduction (这是…), contraste (有 / 没有), une action ou un changement, puis une récapitulation qui combine tout. Gardez le casting existant (man, woman) sauf si l'histoire nécessite quelqu'un de nouveau.
    • La longueur découle du contenu. Par défaut, utilisez autant de phrases qu'il en faut pour donner à chaque nouveau mot 10+ écoutes et couvrir les mots dus. Cela fait généralement 30 à 50 phrases de 2 à 6 mots, soit environ 2 à 4 minutes (environ 4 s par phrase). Si l'apprenant demande une histoire plus longue (« environ 6 minutes », « une longue »), gardez les mêmes 2–3 nouveaux mots et ajoutez des scènes : racontez à nouveau l'histoire, placez les mêmes mots dans une nouvelle situation et ramenez des mots plus anciens. N'ajoutez jamais de nouveaux mots pour remplir le temps.
    • Préférez répéter une phrase à l'identique plutôt que d'inventer une variante. Les répétitions exactes sonnent identiques (elles viennent du cache) et aident à l'écoute.
  4. Rédigez <course>/lessons/NNN-slug/lesson.json. NNN provient du rapport. Ce brouillon est la source de vérité du plan, et vous le réviserez jusqu'à ce que l'apprenant l'approuve. Mettez chaque beat sur une ligne, afin que les numéros de beat soient faciles à voir :

    {"title": "…", "art": "drawn", "new_words": ["…"],
     "beats": [
      {"words": [["这","zhè"],["是","shì"],["一个","yí ge"],["人","rén"]], "show": "man alone", "target": ["man"], "caption": true}
     ]}
    
    • words contient les unités de mots avec la romanisation à tons, en utilisant le sandhi tel qu'il est prononcé (一个 = yí ge). Pas de ponctuation à l'intérieur de words. Chaque beat est lu comme une déclaration se terminant par 。, donc ne prévoyez pas de questions.
    • show est une prose décrivant l'image, pour vous et pour l'art.
    • target liste les identifiants des sprites qui brillent pendant que le beat est prononcé.
    • caption est faux pour environ les 15 derniers % des beats.
    • pause est optionnel : le silence après le beat, par défaut 1,5 s, minimum 1,2. Allongez-le (3–3,5 s) sur un beat suivi d'un mouvement, comme une remise, pour que le mouvement se produise dans le silence.
    • art est drawn quand vous êtes Claude et generated quand vous êtes Codex.

    Exécutez ensuite python3 <skill>/scripts/curriculum.py check <lesson.json>. Corrigez chaque ERREUR et chaque AVERTISSEMENT. Il imprime également une liste numérotée des beats ; conservez-la pour l'étape 8.

  5. Présentez le plan et attendez le feu vert. Avant tout art, narration ou rendu, montrez à l'apprenant un court plan construit à partir du brouillon et de la sortie de check :

    • Histoire : deux à quatre phrases sur ce qui se passe, scène par scène, en anglais simple.
    • Nouveaux mots, chacun avec sa romanisation, sa signification et comment l'image le montre (par exemple « 香蕉 xiāngjiāo, banane : la femme en tient une, puis la tend »), et combien de fois il est entendu.
    • Mots de révision, avec la raison de leur présence (dû, fragile, struggling ou focus) et combien de fois ils sont entendus. Mentionnez tout mot dû que vous avez laissé de côté, et pourquoi.
    • Longueur : le nombre de phrases et la durée approximative de la vidéo (environ 4 s par phrase), plus l'ensemble artistique et la voix.
    • Tout ce qui est nouveau à dessiner ou générer.

    Demandez ensuite, poliment et en une courte ligne, s'ils veulent des changements ou s'ils sont contents que vous génériez. Arrêtez-vous et attendez leur réponse. Traitez tout retour (un sujet différent, d'autres mots, plus long ou plus court, une tournure différente) comme une révision : mettez à jour lesson.json et notes.md si nécessaire, réexécutez check, et présentez le plan révisé de la même manière. Ne générez que lorsque l'apprenant approuve clairement (« ok », « go », « génère ça », « ça a l'air bien »). S'ils ont dit d'emblée qu'ils ne veulent pas réviser le plan (« fais-le », « surprends-moi »), sautez cette étape. S'ils abandonnent l'idée, supprimez le dossier de leçon brouillon.

  6. Art. Réutilisez les sprites dans <course>/art/<set/>. Pour tout ce qui manque, suivez exactement references/art.md, puis ajoutez uniquement les nouveaux fichiers :

    • Claude : écrivez le SVG à la main dans art/drawn/<name>.svg, en vous inspirant des fichiers existants du cours (ou references/examples/ pour un nouveau cours). Un nouveau personnage a besoin des trois expressions. Regardez-le avant de l'utiliser :
      uv run --no-project --with 'cairosvg>=2.7,<3' python -c "import cairosvg,sys; cairosvg.svg2png(url=sys.argv[1], write_to=sys.argv[2], output_width=512)" <svg> /tmp/preview.png
      
    • Codex : utilisez le générateur d'images. Sauvegardez les PNG transparents dans art/generated/ et ajoutez le prompt à art/generated/prompts.md.
  7. Narration. Utilisez la commande qui correspond à voice.engine dans course.json :

    qwen (GPU local) : <env from config> uv run --project <tts_project> --no-sync python <skill>/scripts/audio.py <lesson dir>
    edge (en ligne)  : uv run <skill>/scripts/audio.py <lesson dir>
    

    Exécutez-la en arrière-plan. Les phrases mises en cache sont réutilisées sans GPU ni réseau. Les nouvelles prennent environ 30 s chacune avec qwen sur un GPU (3 prises plus alignement), ou environ une seconde chacune avec edge. Si l'exécution est interrompue, relancez-la : les phrases terminées sont mises en cache. Cela écrit timing.json (temps réels des beats et des mots) et narration.wav. Si un clip mis en cache semble erroné (un mot mal lu, une syllabe manquante), supprimez ses deux fichiers et relancez ; grep -l '<sentence>' <course>/audio-cache/*.json les trouve.

  8. Chorégraphie : écrivez <lesson dir>/story.py. Elle définit scene(t, timing) et renvoie (sprites, marks). Le contrat se trouve dans le docstring de scripts/engine.py.

    • Synchronisez tout mouvement avec les temps des beats (timing["beats"][i]["start"] et ["end"]), jamais avec des secondes littérales.
    • Les positions des mains proviennent de e.anchor / e.on_hand avec e.HAND_L / e.HAND_R. Sur le personnage inversé à droite, HAND_R est la main vers le centre.
    • e.ease donne un mouvement fluide. Pour les anneaux et tout ce qui est lié à « la phrase en cours de prononciation », utilisez index, _ = e.active_beat(t, timing).
    • Regardez d'abord les fichiers story.py antérieurs du cours, et references/examples/story-002-banana.py : une chorégraphie complète de 44 beats avec track (images clés avec arcs), eat (vers la bouche, puis rétrécissement), un point MOUTH, des remises et des anneaux. Copiez ce qui convient. Lorsqu'une aide a été copiée dans une deuxième leçon, déplacez-la dans engine.py lors d'une étape séparée.

    Un squelette :

    import engine as e
    
    def scene(t, timing):
        s = [beat["start"] for beat in timing["beats"]]
        man = {"id": "man", "sprite": "man-0", "x": e.LEFT_X, "y": e.CHARACTER_Y, "h": e.CHARACTER_H}
        woman = {"id": "woman", "sprite": "woman-0", "x": e.RIGHT_X, "y": e.CHARACTER_Y,
                 "h": e.CHARACTER_H, "flip": True, "opacity": e.ease((t - s[2] + 0.8) / 0.8)}
        ax, ay = e.on_hand(man, e.HAND_R, 84)
        return [man, woman, {"id": "apple", "sprite": "apple", "x": ax, "y": ay, "h": 84}], []
    

    Conventions :

    • Utilisez l'expression -1 (déçu) lorsqu'un personnage a les mains vides.
    • Dessinez un anneau (marks) au-dessus d'une main vide pendant que 没有 est prononcé à son sujet.
    • Faites apparaître un personnage ou un objet juste avant le beat qui le nomme.
    • Acheminez les objets pour qu'ils ne passent jamais devant un visage.
  9. Rendu : uv run <skill>/scripts/render.py <lesson dir> prend environ 1 à 2 minutes. Corrigez tout « WARNING beat N: target not on screen » qu'il imprime.

  10. Vérifiez avant de faire un rapport.

    • Ouvrez chaque contact-sheet-N.jpg : une vignette par beat, en milieu de phrase. Vérifiez que la cible brillante correspond à la phrase, que les objets sont dans les mains, que les anneaux sont sur les mains vides, que les visages correspondent, et que rien ne chevauche la bande de sous-titres ni ne sort du cadre.
    • Les mouvements se produisent dans les pauses, donc vérifiez quelques remises ou arrivées : ffmpeg -v error -ss <seconds> -i lesson.mp4 -frames:v 1 /tmp/frame.png. Les temps des beats se trouvent dans timing.json.
    • Corrigez story.py (ou target/caption dans lesson.json ; les deux sont lus au moment du rendu) et refaites le rendu. Si vous changez words, refaites d'abord l'étape 7.
    • Ensuite, indiquez à l'apprenant le chemin de la leçon, les nouveaux mots et les mots révisés.

Autres demandes

  • « Réviser … » / « J'ai oublié … » : créez une leçon sans nouveau mot, ou un seul, construite autour des mots nommés ou des mots de cette leçon. Marquez-les comme struggling si l'apprenant dit qu'ils lui échappent.

  • Vocabulaire ou progression : exécutez le rapport et résumez-le.

  • Voix : la voix du cours se trouve dans course.json : soit qwen (locale, par exemple Serena avec une instruction d'enseignant lent) soit edge (en ligne, par exemple zh-CN-XiaoxiaoNeural au débit -25%).

    • Haut-parleurs Qwen : <env> uv run --project <tts_project> --no-sync python -c "from qwen_tts import Qwen3TTSModel as M; print(M.from_pretrained('Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice').get_supported_speakers())". Cela charge le modèle.
    • Voix edge-tts : uvx edge-tts --list-voices | grep zh-.

    Changer de moteur ou de voix est une modification du bloc voice (voir references/setup.md, étape 3). Cela démarre un nouvel ensemble de clips mis en cache.

Règles

  • Une exécution n'écrit que dans son dossier de leçon, dans audio-cache/ du cours et notes.md, plus les nouveaux fichiers sous <course>/art/<set>/.
  • Les modifications apportées à <skill>/scripts, à l'art existant ou au projet tts sont une étape distincte, effectuée à la demande de l'apprenant.
  • Les leçons importées sans story.py ne sont jamais refondues.

Skills associés