Communitygithub.com

ci-story

Genera videos de historias cortas (ilustradas, narradas y con repetición intensiva) para el aprendizaje de idiomas, diseñado para principiantes absolutos con el enfoque de input comprensible.

¿Qué es ci-story?

ci-story es una habilidad para agentes de IA (Claude, Codex, etc.) que crea automáticamente videos de historias breves para el aprendizaje de idiomas, ideal para principiantes. Cada video presenta un elenco pequeño que actúa una historia sencilla mientras una voz tranquila narra frases cortas que se repiten muchas veces, con un acompañamiento visual que hace evidente su significado. Los subtítulos en la escritura del idioma objetivo más la romanización destacan la palabra que se está hablando. El último ~15% del video se reproduce sin subtítulos para practicar la comprensión auditiva pura. La habilidad admite la configuración de cursos, voces locales (Qwen) o en línea (Edge TTS), y gestión de vocabulario (palabras nuevas, difíciles, sólidas). El flujo de trabajo incluye la planificación docente, generación de arte (SVG dibujado a mano o generado por IA), narración, coreografía y renderizado final en un video MP4. Es compatible con Claude para el arte dibujado a mano y Codex para el arte generado por IA, y se ejecuta mediante scripts de Python en un entorno `uv`.

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

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

¿Qué hace ci-story?

Cada lección es un video corto. Un pequeño elenco actúa una historia simple mientras una voz tranquila narra frases cortas, y cada frase se repite muchas veces con la imagen haciendo obvio su significado. Los subtítulos en la escritura del idioma más la romanización resaltan la palabra que se está hablando. El último ~15% se reproduce sin subtítulos, para escucha pura.

Elabora el plan tú mismo a partir de los datos del curso y la solicitud del aprendiz, luego preséntalo y espera la aprobación antes de generar cualquier cosa (paso 5). Aparte de eso, no hagas preguntas durante la ejecución, excepto durante la configuración inicial y antes de descargas grandes.

Ubicaciones

  • <skill>: la carpeta de esta habilidad (la que contiene este archivo), con scripts/, references/ y assets/.

  • Configuración: ~/.config/ci-story/config.json contiene workspace, tts_project (solo para una voz qwen local) y env opcional. Si falta, o una ruta en ella no existe, sigue references/setup.md primero.

  • Un curso: <workspace>/courses/<lang>/, que contiene:

    • course.json: voz, fuentes
    • notes.md: anulaciones del aprendiz
    • art/drawn/, art/generated/
    • audio-cache/
    • lessons/NNN-slug/

    Cuando el aprendiz no nombre un curso y solo exista uno, usa ese.

  • Nunca ejecutes uv lock --upgrade en el proyecto tts, y nunca cambies una fuente de ruta de torch allí. Puede haber un wheel de torch multigigabyte fijado como archivo local.

Flujo de trabajo para "una historia nueva"

  1. Lee el curso. Ejecuta python3 <skill>/scripts/curriculum.py report <course>. Lee notes.md y los últimos uno o dos archivos lesson.json junto con su story.py, para la continuidad del elenco, accesorios y ayudantes que puedas reutilizar.

  2. Registra lo que dijo el aprendiz.

    • Una palabra que es difícil o se ha olvidado: agrega una línea struggling: 给.
    • Una palabra que conocen bien: agrega solid: 苹果.
    • Palabras de las que quieren más: agrega focus: ….

    Las líneas de etiqueta contienen solo palabras, separadas por comas. Una línea posterior reemplaza a una anterior para la misma palabra, y los comentarios entre (paréntesis) se ignoran.

  3. Planifica, como un profesor:

    • Honra primero la solicitud: su tema, palabras de enfoque o objetivo de repaso.
    • Elige 2–3 palabras nuevas. Cada una debe ser concreta y representable (un objeto, una acción visible, un número, un color o un contraste como tiene/no tiene o grande/pequeño), común en el habla cotidiana y capaz de combinarse con palabras conocidas en oraciones simples. La primera lección de un curso usa solo 5–7 palabras en total (por ejemplo 这 是 一个 más 2–3 sustantivos), cada una escuchada 10+ veces. Dado que cada palabra en una primera lección es nueva, enuméralas todas en new_words.
    • Entreteje cada palabra pendiente al menos 3 veces. Usa las palabras frágiles y struggling con más frecuencia. Deja fuera las palabras solid a menos que la historia las necesite.
    • Cada palabra nueva se escucha al menos 10 veces, cada vez con la imagen mostrando lo que significa.
    • Forma de la historia: introducir (这是…), contrastar (有 / 没有), una acción o cambio, luego un resumen que lo combine todo. Mantén el elenco existente (man, woman) a menos que la historia necesite a alguien nuevo.
    • La longitud se deriva del contenido. Por defecto, usa tantas oraciones como sea necesario para dar a cada palabra nueva 10+ repeticiones y cubrir las palabras pendientes. Normalmente son 30–50 oraciones de 2–6 palabras, aproximadamente 2–4 minutos (unos 4 s por oración). Si el aprendiz pide una historia más larga ("unos 6 minutos", "una larga"), mantén las mismas 2–3 palabras nuevas y agrega escenas: vuelve a contar la historia, pon las mismas palabras en una situación nueva y trae palabras anteriores. Nunca agregues más palabras nuevas para llenar el tiempo.
    • Prefiere repetir una oración exactamente a inventar una variante. Las repeticiones exactas suenan idénticas (vienen del caché) y ayudan a la comprensión auditiva.
  4. Redacta <course>/lessons/NNN-slug/lesson.json. NNN proviene del informe. Este borrador es la fuente de verdad del plan, y lo revisarás hasta que el aprendiz lo apruebe. Pon cada beat en una línea, para que los números de beat sean fáciles de ver:

    {"title": "…", "art": "drawn", "new_words": ["…"],
     "beats": [
      {"words": [["这","zhè"],["是","shì"],["一个","yí ge"],["人","rén"]], "show": "man alone", "target": ["man"], "caption": true}
     ]}
    
    • words contiene las unidades de palabra con romanización marcada de tono, usando sandhi tal como se habla (一个 = yí ge). Sin puntuación dentro de words. Cada beat se lee como una declaración que termina en 。, así que no planees preguntas.
    • show es texto en prosa que describe la imagen, para ti y para el arte.
    • target enumera los ids de sprites que brillan mientras se habla el beat.
    • caption es false para aproximadamente el último 15% de los beats.
    • pause es opcional: el silencio después del beat, por defecto 1.5 s, mínimo 1.2. Hazlo más largo (3–3.5 s) en un beat seguido de movimiento, como una entrega, para que el movimiento ocurra en el silencio.
    • art es drawn cuando eres Claude y generated cuando eres Codex.

    Luego ejecuta python3 <skill>/scripts/curriculum.py check <lesson.json>. Corrige cada ERROR y cada ADVERTENCIA. También imprime una lista numerada de beats; guárdala para el paso 8.

  5. Presenta el plan y espera la aprobación. Antes de cualquier arte, narración o renderizado, muestra al aprendiz un plan breve construido a partir del borrador y la salida de check:

    • Historia: dos a cuatro oraciones sobre lo que sucede, escena por escena, en inglés sencillo.
    • Palabras nuevas, cada una con su romanización, significado y cómo la imagen la muestra (por ejemplo "香蕉 xiāngjiāo, banana: la mujer sostiene una, luego la entrega"), y cuántas veces se escucha.
    • Palabras de repaso, con por qué está cada una (pendiente, frágil, struggling o focus) y cuántas veces se escucha. Menciona cualquier palabra pendiente que hayas dejado fuera y por qué.
    • Duración: el número de oraciones y aproximadamente cuánto dura el video (unos 4 s por oración), más el conjunto de arte y la voz.
    • Cualquier cosa nueva para dibujar o generar.

    Luego pregunta, cortésmente y en una línea breve, si quieren cambios o están contentos de que generes. Detente y espera su respuesta. Trata cualquier comentario (un tema diferente, otras palabras, más largo o más corto, un giro diferente) como una revisión: actualiza lesson.json y notes.md según sea necesario, vuelve a ejecutar check, y presenta el plan revisado de la misma manera. Genera solo una vez que el aprendiz apruebe claramente ("ok", "adelante", "genéralo", "se ve bien"). Si dijeron de antemano que no quieren revisar el plan ("solo hazlo", "sorpréndeme"), omite este paso. Si abandonan la idea, elimina la carpeta del borrador de la lección.

  6. Arte. Reutiliza los sprites en <course>/art/<set>/. Para cualquier cosa que falte, sigue references/art.md exactamente, luego agrega solo archivos nuevos:

    • Claude: escribe SVG a mano en art/drawn/<name>.svg, modelado a partir de los archivos existentes del curso (o references/examples/ en un curso nuevo). Un nuevo personaje necesita las tres expresiones. Míralo antes de usarlo:
      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: usa el generador de imágenes. Guarda PNG transparentes en art/generated/ y agrega el prompt a art/generated/prompts.md.
  7. Narración. Usa el comando que coincida con voice.engine en course.json:

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

    Ejecútalo en segundo plano. Las oraciones en caché se reutilizan sin GPU ni red. Las nuevas toman unos 30 s cada una con qwen en una GPU (3 tomas más alineación), o aproximadamente un segundo cada una con edge. Si la ejecución se interrumpe, vuélvela a ejecutar: las oraciones terminadas están en caché. Esto escribe timing.json (tiempos reales de beat y palabra) y narration.wav. Si un clip en caché suena mal (una palabra leída incorrectamente, una sílaba omitida), elimina sus dos archivos y vuelve a ejecutar; grep -l '<oracion>' <course>/audio-cache/*.json los encuentra.

  8. Coreografía: escribe <lesson dir>/story.py. Define scene(t, timing) y devuelve (sprites, marks). El contrato está en el docstring de scripts/engine.py.

    • Clave todo el movimiento a los tiempos de beat (timing["beats"][i]["start"] y ["end"]), nunca a segundos literales.
    • Las posiciones de las manos provienen de e.anchor / e.on_hand con e.HAND_L / e.HAND_R. En el personaje reflejado a la derecha, HAND_R es la mano hacia el centro.
    • e.ease proporciona movimiento suave. Para anillos y cualquier cosa vinculada a "la oración que se está hablando", usa index, _ = e.active_beat(t, timing).
    • Mira primero los archivos story.py anteriores del curso y references/examples/story-002-banana.py: una coreografía completa de 44 beats con track (fotogramas clave con arcos), eat (a la boca, luego encoger), un punto MOUTH, entregas y anillos. Copia lo que se ajuste. Cuando un ayudante se ha copiado en una segunda lección, muévelo a engine.py en un paso separado.

    Un esqueleto:

    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}], []
    

    Convenciones:

    • Usa la expresión -1 (decepcionado) mientras un personaje está con las manos vacías.
    • Dibuja un anillo (marks) sobre una mano vacía mientras se dice 没有 sobre ella.
    • Trae un personaje u objeto justo antes del beat que lo nombra.
    • Enruta los objetos para que nunca pasen sobre una cara.
  9. Renderizado: uv run <skill>/scripts/render.py <lesson dir> toma aproximadamente 1–2 minutos. Corrige cualquier ADVERTENCIA beat N: target not on screen que imprima.

  10. Verifica antes de informar.

  • Abre cada contact-sheet-N.jpg: un mosaico por beat, a mitad de la oración. Verifica que el objetivo brillante coincida con la oración, los objetos estén en las manos, los anillos estén en las manos vacías, las caras coincidan y nada se superponga a la tira de subtítulos o salga del cuadro.
  • El movimiento ocurre en las pausas, así que verifica algunas entregas o llegadas: ffmpeg -v error -ss <segundos> -i lesson.mp4 -frames:v 1 /tmp/frame.png. Los tiempos de beat están en timing.json.
  • Corrige story.py (o target/caption en lesson.json; ambos se leen en el momento del renderizado) y vuelve a renderizar. Si cambias words, vuelve a ejecutar el paso 7 primero.
  • Luego dile al aprendiz la ruta de la lección, las palabras nuevas y las palabras repasadas.

Otras solicitudes

  • "Repasar …" / "Olvidé …": haz una lección sin palabras nuevas, o con una, construida alrededor de las palabras nombradas o las palabras de esa lección. Márcalas como struggling si el aprendiz dice que se le escapan.

  • Vocabulario o progreso: ejecuta el informe y resúmelo.

  • Voces: la voz del curso está en course.json: ya sea qwen (local, por ejemplo Serena con una instrucción de profesor lento) o edge (en línea, por ejemplo zh-CN-XiaoxiaoNeural a velocidad -25%).

    • Locutores de 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())". Esto carga el modelo.
    • Voces de edge-tts: uvx edge-tts --list-voices | grep zh-.

    Cambiar el motor o la voz es una edición al bloque voice (ver references/setup.md, paso 3). Inicia un conjunto nuevo de clips en caché.

Reglas

  • Una ejecución escribe solo dentro de su carpeta de lección, el audio-cache/ del curso y notes.md, más archivos nuevos bajo <course>/art/<set>/.
  • Los cambios en <skill>/scripts, el arte existente o el proyecto tts son un paso separado, que se realiza cuando el aprendiz lo solicita.
  • Las lecciones importadas sin un story.py nunca se vuelven a renderizar.

Skills relacionados