Was macht ci-story?
Jede Lektion ist ein kurzes Video. Eine kleine Besetzung spielt eine einfache Geschichte, während eine ruhige Stimme kurze Sätze spricht, und jeder Satz wird mehrfach wiederholt, wobei das Bild die Bedeutung offensichtlich macht. Untertitel in der Schrift der Sprache plus Romanisierung heben das gesprochene Wort hervor. Die letzten etwa 15 % laufen ohne Untertitel, zum reinen Hören.
Erarbeite den Plan selbst aus den Kursdaten und der Anfrage des Lernenden, präsentiere ihn dann und warte auf die Zustimmung des Lernenden, bevor du etwas generierst (Schritt 5). Abgesehen davon stellst du während des Laufs keine Fragen, außer beim erstmaligen Einrichten und vor großen Downloads.
Orte
-
<skill>: der Ordner dieses Skills (der Ordner, der diese Datei enthält), mitscripts/,references/undassets/. -
Konfiguration:
~/.config/ci-story/config.jsonenthältworkspace,tts_project(nur für eine lokale Qwen-Stimme) und optionalenv. Fehlt sie oder existiert ein Pfad darin nicht, folge zuerstreferences/setup.md. -
Ein Kurs:
<workspace>/courses/<lang>/, mit:course.json: Stimme, Schriftartennotes.md: Lerner-Überschreibungenart/drawn/,art/generated/audio-cache/lessons/NNN-slug/
Wenn der Lernende keinen Kurs nennt und nur einer existiert, verwende diesen.
-
Führe niemals
uv lock --upgradeim TTS-Projekt aus und ändere niemals eine Torch-Pfadquelle dort. Ein Multi-GB-Torch-Wheel kann als lokale Datei gepinnt sein.
Arbeitsablauf für „eine neue Geschichte“
-
Lies den Kurs. Führe
python3 <skill>/scripts/curriculum.py report <course>aus. Liesnotes.mdsowie die letzten ein oder zweilesson.json-Dateien und ihrestory.py, um Kontinuität der Besetzung, Requisiten und Helfer zu gewährleisten, die du wiederverwenden kannst. -
Notiere, was der Lernende gesagt hat.
- Ein Wort, das schwer fällt oder vergessen wurde: Füge eine Zeile
struggling: 给hinzu. - Ein Wort, das sie gut kennen: Füge
solid: 苹果hinzu. - Wörter, von denen sie mehr möchten: Füge
focus: …hinzu.
Tag-Zeilen enthalten nur Wörter, durch Komma getrennt. Eine spätere Zeile überschreibt eine frühere für dasselbe Wort, und Bemerkungen in (Klammern) werden ignoriert.
- Ein Wort, das schwer fällt oder vergessen wurde: Füge eine Zeile
-
Plane wie ein Lehrer:
- Beachte zuerst die Anfrage: ihr Thema, Fokus-Wörter oder Wiederholungsziel.
- Wähle 2–3 neue Wörter. Jedes muss konkret und darstellbar sein (ein Gegenstand, eine sichtbare Handlung, eine Zahl, eine Farbe oder ein Gegensatz wie haben/nicht haben oder groß/klein), im alltäglichen Sprachgebrauch üblich und mit bekannten Wörtern zu einfachen Sätzen kombinierbar. Die erste Lektion eines Kurses verwendet insgesamt nur 5–7 Wörter (z. B. 这 是 一个 plus 2–3 Nomen), jedes mindestens 10 Mal gehört. Da in einer ersten Lektion jedes Wort neu ist, liste sie alle in
new_words. - Baue jedes fällige Wort mindestens 3 Mal ein. Verwende fragile- und
struggling-Wörter häufiger. Lassesolid-Wörter weg, es sei denn, die Geschichte braucht sie. - Jedes neue Wort wird mindestens 10 Mal gehört, jedes Mal mit dem Bild, das seine Bedeutung zeigt.
- Geschichtsform: Einführung (这是…), Gegensatz (有 / 没有), eine Handlung oder Veränderung, dann eine Zusammenfassung, die alles kombiniert. Behalte die bestehende Besetzung (
man,woman) bei, es sei denn, die Geschichte braucht jemand Neues. - Die Länge ergibt sich aus dem Inhalt. Standardmäßig verwende so viele Sätze, wie nötig sind, um jedes neue Wort 10+ Mal zu hören und die fälligen Wörter abzudecken. Das sind normalerweise 30–50 Sätze mit 2–6 Wörtern, etwa 2–4 Minuten (ca. 4 s pro Satz). Wenn der Lernende um eine längere Geschichte bittet („etwa 6 Minuten“, „eine lange“), behalte dieselben 2–3 neuen Wörter und füge Szenen hinzu: Erzähle die Geschichte nach, setze dieselben Wörter in eine neue Situation und bringe ältere Wörter zurück. Füge niemals neue Wörter hinzu, um die Zeit zu füllen.
- Bevorzuge die exakte Wiederholung eines Satzes gegenüber dem Erfinden einer Variante. Exakte Wiederholungen klingen identisch (sie kommen aus dem Cache) und helfen beim Hören.
-
Erstelle den Entwurf
<course>/lessons/NNN-slug/lesson.json>. NNN stammt aus dem Bericht. Dieser Entwurf ist die Quelle der Wahrheit des Plans, und du wirst ihn überarbeiten, bis der Lernende ihn genehmigt. Setze jeden Beat in eine Zeile, damit Beat-Nummern leicht zu sehen sind:{"title": "…", "art": "drawn", "new_words": ["…"], "beats": [ {"words": [["这","zhè"],["是","shì"],["一个","yí ge"],["人","rén"]], "show": "man alone", "target": ["man"], "caption": true} ]}wordsenthält die Worteinheiten mit tonmarkierter Romanisierung unter Verwendung des Sandhi, wie gesprochen (一个 = yí ge). Keine Satzzeichen innerhalb vonwords. Jeder Beat wird als Aussage gelesen, die mit 。 endet, also plane keine Fragen.showist Prosa, die das Bild beschreibt, für dich und für die Kunst.targetlistet die Sprite-IDs auf, die leuchten, während der Beat gesprochen wird.captionist für etwa die letzten 15 % der Beats auffalsegesetzt.pauseist optional: die Stille nach dem Beat, Standard 1,5 s, Minimum 1,2. Mache sie länger (3–3,5 s) nach einem Beat, dem eine Bewegung folgt, z. B. eine Übergabe, damit die Bewegung in der Stille stattfindet.artistdrawn, wenn du Claude bist, undgenerated, wenn du Codex bist.
Führe dann
python3 <skill>/scripts/curriculum.py check <lesson.json>aus. Behebe jeden ERROR und jede WARNING. Es gibt auch eine nummerierte Liste der Beats aus; behalte sie für Schritt 8. -
Präsentiere den Plan und warte auf die Freigabe. Bevor du Kunst, Erzählung oder Rendering erstellst, zeige dem Lernenden einen kurzen Plan, der aus dem Entwurf und der
check-Ausgabe erstellt wurde:- Geschichte: zwei bis vier Sätze darüber, was passiert, Szene für Szene, in einfachem Englisch.
- Neue Wörter, jedes mit seiner Romanisierung, Bedeutung und wie das Bild es zeigt (z. B. „香蕉 xiāngjiāo, Banane: die Frau hält eine, dann gibt sie sie weiter“) und wie oft es gehört wird.
- Wiederholungswörter, mit dem Grund, warum jedes enthalten ist (fällig, fragil, kämpfend oder Fokus) und wie oft es gehört wird. Erwähne jedes fällige Wort, das du weggelassen hast, und warum.
- Länge: die Anzahl der Sätze und wie lange das Video ungefähr läuft (ca. 4 s pro Satz), plus die Kunstsammlung und die Stimme.
- Alles, was neu zu zeichnen oder zu generieren ist.
Frage dann höflich und in einer kurzen Zeile, ob sie Änderungen wünschen oder ob du weitermachen sollst. Halte inne und warte auf ihre Antwort. Behandle jedes Feedback (ein anderes Thema, andere Wörter, länger oder kürzer, eine andere Wendung) als Überarbeitung: aktualisiere
lesson.jsonundnotes.mdnach Bedarf, führecheckerneut aus und präsentiere den überarbeiteten Plan auf dieselbe Weise. Generiere erst, wenn der Lernende klar zustimmt („ok“, „los“, „generiere es“, „sieht gut aus“). Wenn sie von vornherein gesagt haben, dass sie den Plan nicht überprüfen möchten („mach einfach“, „überrasch mich“), überspringe diesen Schritt. Wenn sie die Idee fallen lassen, lösche den Entwurfs-Lektion-Ordner. -
Kunst. Verwende die Sprites im
<course>/art/<set>/wieder. Für alles Fehlende folge genaureferences/art.mdund füge dann nur neue Dateien hinzu:- Claude: Schreibe SVG von Hand in
art/drawn/<name>.svg, modelliert nach den vorhandenen Dateien des Kurses (oderreferences/examples/in einem neuen Kurs). Eine neue Figur benötigt alle drei Ausdrücke. Sieh es dir an, bevor du es verwendest: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: Verwende den Bildgenerator. Speichere transparente PNGs in
art/generated/und hänge den Prompt anart/generated/prompts.mdan.
- Claude: Schreibe SVG von Hand in
-
Vertonung. Verwende den Befehl, der zu
voice.engineincourse.jsonpasst:qwen (lokale GPU): <env from config> uv run --project <tts_project> --no-sync python <skill>/scripts/audio.py <lesson dir> edge (online): uv run <skill>/scripts/audio.py <lesson dir>Führe ihn im Hintergrund aus. Zwischengespeicherte Sätze werden ohne GPU oder Netzwerk wiederverwendet. Neue brauchen mit qwen auf einer GPU etwa 30 s pro Stück (3 Takes plus Ausrichtung) oder mit edge etwa eine Sekunde. Wenn der Lauf unterbrochen wird, führe ihn erneut aus: fertige Sätze sind zwischengespeichert. Dies schreibt
timing.json(echte Beat- und Wortzeiten) undnarration.wav. Wenn ein zwischengespeicherter Clip falsch klingt (ein falsch gelesenes Wort, eine ausgelassene Silbe), lösche seine beiden Dateien und führe ihn erneut aus;grep -l '<sentence>' <course>/audio-cache/*.jsonfindet sie. -
Choreografie: Schreibe
<lesson dir>/story.py. Es definiertscene(t, timing)und gibt(sprites, marks)zurück. Der Vertrag steht im Docstring vonscripts/engine.py.- Binde alle Bewegungen an Beat-Zeiten (
timing["beats"][i]["start"]und["end"]), niemals an buchstäbliche Sekunden. - Handpositionen werden von
e.anchor/e.on_handmite.HAND_L/e.HAND_Rbezogen. Beim gespiegelten rechten Charakter istHAND_Rdie Hand zur Mitte hin. e.easesorgt für sanfte Bewegungen. Für Ringe und alles, was an „den Satz, der gerade gesprochen wird“ gebunden ist, verwendeindex, _ = e.active_beat(t, timing).- Sieh dir zuerst die früheren
story.py-Dateien des Kurses an undreferences/examples/story-002-banana.py: eine vollständige 44-Beat-Choreografie mittrack(Keyframes mit Bögen),eat(zum Mund, dann schrumpfen), einemMOUTH-Punkt, Übergaben und Ringen. Kopiere, was passt. Wenn ein Helfer in eine zweite Lektion kopiert wurde, verschiebe ihn in einem separaten Schritt inengine.py.
Ein Gerüst:
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}], []Konventionen:
- Verwende den Ausdruck
-1(enttäuscht), während eine Figur leerhändig ist. - Zeichne einen Ring (
marks) über eine leere Hand, während 没有 darüber gesagt wird. - Bringe eine Figur oder ein Objekt kurz vor dem Beat herein, der sie benennt.
- Leite Objekte so, dass sie nie über ein Gesicht fahren.
- Binde alle Bewegungen an Beat-Zeiten (
-
Rendern:
uv run <skill>/scripts/render.py <lesson dir>dauert etwa 1–2 Minuten. Behebe jedesWARNING beat N: target not on screen, das es ausgibt. -
Überprüfen vor dem Bericht.
- Öffne jedes
contact-sheet-N.jpg: eine Kachel pro Beat, in der Satzmitte. Überprüfe, dass das leuchtende Ziel zum Satz passt, Objekte in den Händen sitzen, Ringe auf leeren Händen sitzen, Gesichter passen und nichts den Untertitelstreifen überlappt oder den Rahmen verlässt. - Bewegungen finden in den Pausen statt, überprüfe daher ein paar Übergaben oder Ankünfte:
ffmpeg -v error -ss <seconds> -i lesson.mp4 -frames:v 1 /tmp/frame.png. Beat-Zeiten stehen intiming.json. - Korrigiere
story.py(odertarget/captionin lesson.json; beides wird zum Renderzeitpunkt gelesen) und rendere neu. Wenn duwordsänderst, führe zuerst Schritt 7 erneut aus. - Teile dem Lernenden dann den Lektionspfad, die neuen Wörter und die wiederholten Wörter mit.
Andere Anfragen
-
„Wiederhole …“ / „Ich habe … vergessen“: Erstelle eine Lektion ohne neue Wörter oder mit einem, aufgebaut um die genannten Wörter oder die Wörter dieser Lektion. Markiere sie als
struggling, wenn der Lernende sagt, dass sie ihnen entfallen. -
Wortschatz oder Fortschritt: Führe den Bericht aus und fasse ihn zusammen.
-
Stimmen: Die Stimme des Kurses steht in
course.json: entwederqwen(lokal, z. B.Serenamit einer Langsam-Lehrer-Anweisung) oderedge(online, z. B.zh-CN-XiaoxiaoNeuralmit Rate-25%).- Qwen-Sprecher:
<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())". Dies lädt das Modell. - edge-tts-Stimmen:
uvx edge-tts --list-voices | grep zh-.
Das Wechseln der Engine oder Stimme ist eine Bearbeitung des
voice-Blocks (siehereferences/setup.md, Schritt 3). Es startet einen neuen Satz zwischengespeicherter Clips. - Qwen-Sprecher:
Regeln
- Ein Lauf schreibt nur in seinen Lektionsordner, das
audio-cache/des Kurses undnotes.md, plus neue Dateien unter<course>/art/<set>/. - Änderungen an
<skill>/scripts, vorhandener Kunst oder dem TTS-Projekt sind ein separater Schritt, der nur auf Anfrage des Lernenden durchgeführt wird. - Importierte Lektionen ohne
story.pywerden niemals neu gerendert.