Communitygithub.com

ci-story

Gere vídeos de história com input compreensível para iniciantes (ilustrados, narrados, com muita repetição) para um curso de idiomas do aprendiz, atualmente Mandarim. Use quando o aprendiz pedir uma nova história ou lição, uma revisão, uma história sobre um tópico ou certas palavras, disser que uma palavra é difícil ou já conhecida, quiser ver o vocabulário do curso ou as vozes disponíveis, ou quiser configurar o ci-story.

O que é ci-story?

O ci-story é uma skill para agentes de IA (como Claude Code, Cursor, Codex) que cria vídeos de histórias curtas e altamente repetitivas para aprendizado de idiomas, focado atualmente em Mandarim. Cada lição é um vídeo de 2 a 4 minutos com um pequeno elenco, narração calma, legendas e romanização destacando a palavra falada, e uma seção final sem legendas para treino auditivo puro. A skill segue um fluxo de trabalho estruturado: primeiro lê os dados do curso e o pedido do aprendiz, planeja a história com 2-3 palavras novas e revisão de palavras programadas, apresenta o plano para aprovação, e só então gera arte, narração e coreografia. Ela suporta dois mecanismos de narração: um local via Qwen (GPU) ou online via edge-tts. A skill é configurável por um arquivo JSON e requer um espaço de trabalho com estrutura de curso bem definida.

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

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

O que ci-story faz?

Cada lição é um vídeo curto. Um pequeno elenco representa uma história simples enquanto uma voz calma narra frases curtas, e cada frase se repete muitas vezes com a imagem tornando seu significado óbvio. Legendas na escrita do idioma mais romanização destacam a palavra que está sendo falada. Os últimos ~15% rodam sem legendas, para audição pura.

Elabore o plano você mesmo a partir dos dados do curso e do pedido do aprendiz, depois apresente-o e aguarde o aval do aprendiz antes de gerar qualquer coisa (etapa 5). Fora isso, não faça perguntas durante a execução, exceto durante a configuração inicial e antes de grandes downloads.

Locais

  • <skill>: a pasta desta skill (a que contém este arquivo), com scripts/, references/ e assets/.

  • Config: ~/.config/ci-story/config.json contém workspace, tts_project (apenas para uma voz qwen local) e env opcional. Se estiver faltando, ou se um caminho nele não existir, siga references/setup.md primeiro.

  • Um curso: <workspace>/courses/<lang>/, contendo:

    • course.json: voz, fontes
    • notes.md: substituições do aprendiz
    • art/drawn/, art/generated/
    • audio-cache/
    • lessons/NNN-slug/

    Quando o aprendiz não nomeia um curso e apenas um existe, use esse.

  • Nunca execute uv lock --upgrade no projeto tts, e nunca altere uma origem de caminho torch lá. Um wheel torch de vários GB pode estar fixado como um arquivo local.

Fluxo de trabalho para "uma nova história"

  1. Leia o curso. Execute python3 <skill>/scripts/curriculum.py report <course>. Leia notes.md, e o último ou dois arquivos lesson.json mais seus story.py, para continuidade de elenco, adereços e ajudantes que você pode reutilizar.

  2. Registre o que o aprendiz disse.

    • Uma palavra que é difícil ou esquecida: adicione uma linha struggling: 给.
    • Uma palavra que eles conhecem bem: adicione solid: 苹果.
    • Palavras que eles querem mais: adicione focus: ….

    As linhas de marcação contêm apenas palavras, separadas por vírgula. Uma linha posterior substitui uma anterior para a mesma palavra, e observações entre (parênteses) são ignoradas.

  3. Planeje, como um professor:

    • Atenda ao pedido primeiro: seu tópico, palavras focadas ou alvo de revisão.
    • Escolha 2–3 palavras novas. Cada uma deve ser concreta e mostrável (um objeto, uma ação visível, um número, uma cor ou um contraste como tem/não tem ou grande/pequeno), comum na fala cotidiana e capaz de se combinar com palavras conhecidas em frases simples. A primeira lição de um curso usa apenas 5–7 palavras no total (por exemplo 这 是 一个 mais 2–3 substantivos), cada uma ouvida 10+ vezes. Como toda palavra em uma primeira lição é nova, liste todas em new_words.
    • Incorpore cada palavra devida pelo menos 3 vezes. Use palavras frágeis e struggling com mais frequência. Deixe palavras solid de fora, a menos que a história precise delas.
    • Cada palavra nova é ouvida pelo menos 10 vezes, cada vez com a imagem mostrando o que significa.
    • Forma da história: introduza (这是…), contraste (有 / 没有), uma ação ou mudança, depois um resumo que combina tudo. Mantenha o elenco existente (man, woman) a menos que a história precise de alguém novo.
    • O comprimento segue do conteúdo. Por padrão, use quantas frases forem necessárias para dar a cada palavra nova 10+ audições e cobrir as palavras devidas. Isso geralmente é 30–50 frases de 2–6 palavras, aproximadamente 2–4 minutos (cerca de 4 s por frase). Se o aprendiz pedir uma história mais longa ("cerca de 6 minutos", "uma longa"), mantenha as mesmas 2–3 palavras novas e adicione cenas: reconte a história, coloque as mesmas palavras em uma nova situação e traga palavras mais antigas de volta. Nunca adicione mais palavras novas para preencher o tempo.
    • Prefira repetir uma frase exatamente a inventar uma variante. Repetições exatas soam idênticas (vêm do cache) e ajudam na audição.
  4. Elabore <course>/lessons/NNN-slug/lesson.json. NNN vem do relatório. Este rascunho é a fonte da verdade do plano, e você o revisará até o aprendiz aprovar. Coloque cada batida em uma linha, para que os números das batidas sejam fáceis de ver:

    {"title": "…", "art": "drawn", "new_words": ["…"],
     "beats": [
      {"words": [["这","zhè"],["是","shì"],["一个","yí ge"],["人","rén"]], "show": "man alone", "target": ["man"], "caption": true}
     ]}
    
    • words contém as unidades de palavras com romanização com marcação de tom, usando sandhi como falado (一个 = yí ge). Sem pontuação dentro de words. Cada batida é lida como uma declaração terminada em ., então não planeje perguntas.
    • show é uma prosa descrevendo a imagem, para você e para a arte.
    • target lista os ids de sprites que brilham enquanto a batida é falada.
    • caption é falso para aproximadamente os últimos 15% das batidas.
    • pause é opcional: o silêncio após a batida, padrão 1,5 s, mínimo 1,2. Torne-o mais longo (3–3,5 s) em uma batida seguida por movimento, como uma entrega, para que o movimento ocorra no silêncio.
    • art é drawn quando você é Claude e generated quando você é Codex.

    Em seguida, execute python3 <skill>/scripts/curriculum.py check <lesson.json>. Corrija cada ERROR e cada WARNING. Ele também imprime uma lista numerada de batidas; mantenha-a para a etapa 8.

  5. Apresente o plano e aguarde o aval. Antes de qualquer arte, narração ou renderização, mostre ao aprendiz um plano curto construído a partir do rascunho e da saída de check:

    • História: duas a quatro frases sobre o que acontece, cena por cena, em português claro.
    • Palavras novas, cada uma com sua romanização, significado e como a imagem a mostra (por exemplo "香蕉 xiāngjiāo, banana: a mulher segura uma, depois a entrega"), e quantas vezes é ouvida.
    • Palavras de revisão, com o motivo de cada uma estar incluída (devida, frágil, struggling ou focus) e quantas vezes é ouvida. Mencione qualquer palavra devida que você deixou de fora, e por quê.
    • Duração: o número de frases e aproximadamente quanto tempo o vídeo dura (cerca de 4 s por frase), além do conjunto de arte e da voz.
    • Qualquer coisa nova para desenhar ou gerar.

    Em seguida, pergunte, educadamente e em uma linha curta, se eles querem mudanças ou se estão felizes para você gerar. Pare e aguarde a resposta deles. Trate qualquer feedback (um tópico diferente, outras palavras, mais longo ou mais curto, uma reviravolta diferente) como uma revisão: atualize lesson.json e notes.md conforme necessário, execute check novamente e apresente o plano revisado da mesma forma. Gere apenas quando o aprendiz aprovar claramente ("ok", "vai", "gera", "parece bom"). Se eles disseram de antemão que não querem revisar o plano ("apenas faça", "surpreenda-me"), pule esta etapa. Se eles desistirem da ideia, exclua a pasta de lição do rascunho.

  6. Arte. Reutilize os sprites em <course>/art/<set>/. Para qualquer coisa faltando, siga references/art.md exatamente, depois adicione apenas novos arquivos:

    • Claude: escreva SVG manualmente em art/drawn/<nome>.svg, modelado nos arquivos existentes do curso (ou references/examples/ em um novo curso). Um novo personagem precisa de todas as três expressões. Veja-o antes de usá-lo:
      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: use o gerador de imagens. Salve PNGs transparentes em art/generated/ e anexe o prompt a art/generated/prompts.md.
  7. Narração. Use o comando que corresponde a voice.engine em course.json:

    qwen (GPU local): <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>
    

    Execute-o em segundo plano. Frases em cache são reutilizadas sem GPU ou rede. Novas levam cerca de 30 s cada com qwen em uma GPU (3 takes mais alinhamento), ou cerca de um segundo cada com edge. Se a execução for interrompida, execute novamente: as frases finalizadas estão em cache. Isso escreve timing.json (tempos reais de batida e palavra) e narration.wav. Se um clipe em cache soar errado (uma palavra mal lida, uma sílaba omitida), exclua seus dois arquivos e execute novamente; grep -l '<sentence>' <course>/audio-cache/*.json os encontra.

  8. Coreografia: escreva <lesson dir>/story.py. Ele define scene(t, timing) e retorna (sprites, marks). O contrato está no docstring de scripts/engine.py.

    • Key all movement to beat times (timing["beats"][i]["start"] e ["end"]), nunca a segundos literais.
    • Posições das mãos vêm de e.anchor / e.on_hand com e.HAND_L / e.HAND_R. No personagem espelhado à direita, HAND_R é a mão em direção ao centro.
    • e.ease dá movimento suave. Para anéis e qualquer coisa ligada a "a frase sendo falada", use index, _ = e.active_beat(t, timing).
    • Veja primeiro os arquivos story.py anteriores do curso e references/examples/story-002-banana.py: uma coreografia completa de 44 batidas com track (quadros-chave com arcos), eat (para a boca, depois encolher), um ponto MOUTH, entregas e anéis. Copie o que for adequado. Quando um ajudante foi copiado para uma segunda lição, mova-o para engine.py em uma etapa separada.

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

    Convenções:

    • Use expressão -1 (decepcionado) enquanto um personagem está de mãos vazias.
    • Desenhe um anel (marks) sobre uma mão vazia enquanto 没有 é dito sobre ela.
    • Traga um personagem ou objeto para dentro logo antes da batida que o nomeia.
    • Roteie objetos para que nunca passem sobre um rosto.
  9. Renderize: uv run <skill>/scripts/render.py <lesson dir> leva cerca de 1–2 minutos. Corrija qualquer WARNING beat N: target not on screen que ele imprimir.

  10. Verifique antes de relatar.

  • Abra cada contact-sheet-N.jpg: um bloco por batida, no meio da frase. Verifique se o alvo brilhante corresponde à frase, os objetos estão nas mãos, os anéis estão nas mãos vazias, os rostos correspondem e nada sobrepõe a faixa de legenda ou sai do quadro.
  • O movimento ocorre nas pausas, então verifique algumas entregas ou chegadas: ffmpeg -v error -ss <seconds> -i lesson.mp4 -frames:v 1 /tmp/frame.png. Os tempos das batidas estão em timing.json.
  • Corrija story.py (ou target/caption em lesson.json; ambos são lidos no momento da renderização) e renderize novamente. Se você alterar words, execute novamente a etapa 7 primeiro.
  • Em seguida, informe ao aprendiz o caminho da lição, as novas palavras e as palavras revisadas.

Outros pedidos

  • "Revisar …" / "Eu esqueci …": faça uma lição sem palavras novas, ou com uma, construída em torno das palavras nomeadas ou das palavras dessa lição. Marque-as como struggling se o aprendiz disser que estão escapando.

  • Vocabulário ou progresso: execute o relatório e resuma-o.

  • Vozes: a voz do curso está em course.json: ou qwen (local, por exemplo Serena com uma instrução de professor lento) ou edge (online, por exemplo zh-CN-XiaoxiaoNeural na taxa -25%).

    • Locutores 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())". Isso carrega o modelo.
    • Vozes edge-tts: uvx edge-tts --list-voices | grep zh-.

    Trocar de mecanismo ou voz é uma edição no bloco voice (veja references/setup.md, etapa 3). Isso inicia um novo conjunto de clipes em cache.

Regras

  • Uma execução escreve apenas dentro de sua pasta de lição, no audio-cache/ do curso e notes.md, mais novos arquivos sob <course>/art/<set>/.
  • Alterações em <skill>/scripts, arte existente ou no projeto tts são uma etapa separada, feita quando o aprendiz pede.
  • Lições importadas sem um story.py nunca são renderizadas novamente.

Habilidades Relacionadas