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), avecscripts/,references/etassets/. -
Configuration :
~/.config/ci-story/config.jsoncontientworkspace,tts_project(uniquement pour une voix qwen locale) etenven option. S'il est manquant ou si un chemin qu'il contient n'existe pas, suivez d'abordreferences/setup.md. -
Un cours :
<workspace>/courses/<lang>/, contenant :course.json: voix, policesnotes.md: préférences de l'apprenantart/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 --upgradedans 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 »
-
Lisez le cours. Exécutez
python3 <skill>/scripts/curriculum.py report <course>. Liseznotes.md, ainsi que le ou les deux derniers fichierslesson.jsonet leurstory.py, pour la continuité du casting, des accessoires et des aides que vous pouvez réutiliser. -
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.
- Un mot difficile ou oublié : ajoutez une ligne
-
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
strugglingplus souvent. Laissez de côté les motssolidsauf 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.
-
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} ]}wordscontient 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 dewords. Chaque beat est lu comme une déclaration se terminant par 。, donc ne prévoyez pas de questions.showest une prose décrivant l'image, pour vous et pour l'art.targetliste les identifiants des sprites qui brillent pendant que le beat est prononcé.captionest faux pour environ les 15 derniers % des beats.pauseest 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.artestdrawnquand vous êtes Claude etgeneratedquand 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. -
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.jsonetnotes.mdsi nécessaire, réexécutezcheck, 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. -
Art. Réutilisez les sprites dans
<course>/art/<set/>. Pour tout ce qui manque, suivez exactementreferences/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 (oureferences/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.
- Claude : écrivez le SVG à la main dans
-
Narration. Utilisez la commande qui correspond à
voice.enginedanscourse.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) etnarration.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/*.jsonles trouve. -
Chorégraphie : écrivez
<lesson dir>/story.py. Elle définitscene(t, timing)et renvoie(sprites, marks). Le contrat se trouve dans le docstring descripts/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_handavece.HAND_L/e.HAND_R. Sur le personnage inversé à droite,HAND_Rest la main vers le centre. e.easedonne un mouvement fluide. Pour les anneaux et tout ce qui est lié à « la phrase en cours de prononciation », utilisezindex, _ = e.active_beat(t, timing).- Regardez d'abord les fichiers
story.pyantérieurs du cours, etreferences/examples/story-002-banana.py: une chorégraphie complète de 44 beats avectrack(images clés avec arcs),eat(vers la bouche, puis rétrécissement), un pointMOUTH, 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 dansengine.pylors 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.
- Synchronisez tout mouvement avec les temps des beats (
-
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. -
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 danstiming.json. - Corrigez
story.py(outarget/captiondans lesson.json ; les deux sont lus au moment du rendu) et refaites le rendu. Si vous changezwords, 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.
- Ouvrez chaque
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
strugglingsi 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: soitqwen(locale, par exempleSerenaavec une instruction d'enseignant lent) soitedge(en ligne, par exemplezh-CN-XiaoxiaoNeuralau 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(voirreferences/setup.md, étape 3). Cela démarre un nouvel ensemble de clips mis en cache. - Haut-parleurs Qwen :
Règles
- Une exécution n'écrit que dans son dossier de leçon, dans
audio-cache/du cours etnotes.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.pyne sont jamais refondues.