Communitygithub.com

johnwhoyou/screen-video

Grave uma demonstração em vídeo com legendas e narração opcional de um aplicativo web em execução com Playwright — cursor suave, sons de teclado e clique, zoom e panorama, nitidez de 2× a 4K — e entregue onde for necessário: um .mp4 simples, inline em um PR ou issue do GitHub, em uma issue do Linear, no Slack ou em um tutorial para clientes.

O que é screen-video?

Esta skill permite criar gravações de tela profissionalmente narradas e legendadas de aplicações web, utilizando Playwright para controle preciso do navegador. Ela gera vídeos MP4 H.264 a 60 FPS com cursor animado, zoom suave e efeitos sonoros de clique e teclado, tudo sincronizado com a narração gerada por TTS (Google Gemini ou OpenAI). A skill é ideal para criar demos de funcionalidades, walkthroughs de UI, vídeos para PRs/issues, tutoriais para clientes ou reprodução de bugs. Ela suporta entrega direta em formato .mp4, ou integração com GitHub (uploads inline via navegador), Linear (via MCP), Slack (via MCP) e outras plataformas. O processo é dividido em etapas: planejamento do roteiro, gravação com script de fluxo, verificação de qualidade e entrega no destino escolhido. Inclui recursos avançados como simulação de email no Gmail, zoom e panorâmica suaves, e dicas para evitar retrabalhos.

Funciona com~Claude Code✓Codex CLI✓Cursor✓Gemini CLI
npx skills add https://github.com/johnwhoyou/screen-video/tree/HEAD/plugins/screen-video/skills/screen-video

Perguntar na sua IA favorita

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

Prévia

Do README da skill

walkthrough.mp4

Documentação

Screen video

O screencast do Chrome grava o próprio navegador, reconstruído como um .mp4 H.264 a 60 FPS. Legendas, cursor, zoom e sons são adicionados depois, então a gravação permanece suave independentemente do que a página esteja fazendo.

Os scripts resolvem o Playwright a partir do node_modules do repositório atual, portanto execute-os a partir da raiz do repositório. <skill-dir> é a pasta desta skill.

0. Pergunte primeiro

A menos que a solicitação já diga, pergunte em uma única mensagem:

  1. Qual história? Qual aplicativo e URL, quais páginas, os 3 a 6 momentos que o espectador deve ver. Quem assiste (colegas de equipe, um revisor, um cliente)?
  2. Voz? Apenas legendas ou narrado (narração por IA)? Vídeos para clientes são sempre narrados.
  3. Para onde vai? Apenas o arquivo .mp4 (padrão), um PR ou issue do GitHub, uma issue do Linear, Slack ou outro lugar. Pergunte pelo PR/issue/canal; nunca crie um ticket em nome de outra pessoa.
  4. Quais dados? Qual banco de dados local ou conta será usada para a gravação. Nunca produção, nunca dados reais de clientes.

1. Gravar

  1. Execute o aplicativo localmente e abra cada página uma vez primeiro: a primeira compilação de um servidor de desenvolvimento pode aparecer no vídeo.

  2. Escreva um script de fluxo baseado em scripts/record-lib.mjs (copie examples/playwright-site/flow.mjs, uma gravação executável de um site público). Use step(id, caption, action) (ou caption() simples) para colocar uma linha de legenda na tela para cada etapa: centralizado na parte inferior, elevado acima da barra de reprodução do player, translúcido (captionStyle: 'top' para um banner). Mantenha em cerca de 1 minuto. Clique com click(locator) / hover(locator) do fluxo, não com locator.click(): eles deslizam o cursor desenhado até o alvo primeiro. Um locator.click() simples ainda funciona, mas o cursor salta. zoom(locator, { scale }) aproxima suavemente de um alvo, unzoom() volta, e click(locator, { zoom: true }) aproxima antes de um clique. O zoom permanece até que você o mova; um clique fora da área ampliada primeiro move o panorama para lá.

    Orçamento de zoom: deve parecer que uma pessoa está se inclinando, não um reflexo.

    • Aplique zoom apenas quando um texto pequeno é o ponto central: um diff, um valor que mudou, a mensagem de um diálogo. Nunca para navegação, abrir um menu ou pressionar um botão que qualquer um pode ver.
    • Cerca de um zoom a cada 15–20 segundos. Dois em sequência: faça um panorama entre eles (chame zoom no próximo alvo) em vez de sair e voltar.
    • Permaneça com zoom enquanto as próximas ações ocorrerem na mesma área; use unzoom() quando a história se mover para outro lugar.
    • Escala 1,3–1,5. Vá para 2 apenas para algo muito pequeno, como um único ícone. Mantenha por pelo menos 2 segundos.
    • Prefira uma pausa narrada a um zoom: se a linha for legível no tamanho normal, não aplique zoom.

    Digite com type(locator, text, { clear }): digita no ritmo de uma pessoa. click e type registram cada clique e tecla pressionada em <video>.sfx.json, e narrate.mjs coloca os sons gravados sob a voz: amostras de clique de mouse e teclado mecânico de sounds/. SFX_VOLUME os escala (padrão 1), SFX=0 para nenhum. page.keyboard.type e locator.click() simples não produzem som.

  3. node flow.mjs imprime o caminho do .mp4. Precisa do ffmpeg ($FFMPEG ou PATH; sem ffmpeg? npm i --prefix <scratch> ffmpeg-static).

Qualidade

record(flow, { scale }) define a página no size (padrão 1280×800 CSS px, para a UI parecer normal) e a renderiza em scale× densidade de pixels: 2 (padrão) é 2560×1600, 3 é 3840×2400. O texto fica mais nítido, não menor. Espere aproximadamente 15–40 MB por minuto em 2 e 2–3× isso em 3. Para um destino com limite de tamanho (plano gratuito do GitHub 10 MB, prévias do Slack), use scale: 1 ou CRF=24.

Narração (quando solicitado voz)

  1. Escreva script.json: [{ "id": "intro", "say": "…" }, …], uma linha falada por step id. Escreva para ser ouvido: frases curtas, números por extenso ("mil e quinhentos").
  2. GOOGLE_API_KEY=… node <skill-dir>/scripts/tts.mjs script.json <audio-dir> escreve um WAV por linha e durations.json. O Gemini 3.8 Flash TTS (TTS_MODEL, TTS_VOICE para sobrescrever) soou mais natural que o OpenAI gpt-4o-mini-tts. Obtenha uma chave em aistudio.google.com.
  3. Grave com { narration: '<audio-dir>/durations.json' }. Cada step espera até que sua linha termine, e os horários de início ficam em <video>.mp4.marks.json.
  4. node <skill-dir>/scripts/narrate.mjs <video.mp4> <audio-dir> <out.mp4> multiplexa os clipes e sons (recodifica em CRF, padrão 18). Entregue <out.mp4>.

Peculiaridades do TTS:

  • Envie apenas a linha. Um prefixo "Diga calorosamente: …" é lido em voz alta, e systemInstruction retorna 400.
  • O Gemini retorna um WAV completo mais um bloco de proveniência; encapsular isso em um segundo cabeçalho faz som estático em ambas as extremidades de cada linha. O tts.mjs extrai o bloco de dados por esse motivo; mantenha isso.
  • Para verificar a sincronia, transcreva o áudio do mp4 com timestamps (OpenAI whisper-1, response_format=verbose_json) e compare com as marcas.

Peculiaridades que custam uma regravação

  • Limpe uma entrada pré-preenchida com .fill('') antes de pressSequentially, que apenas anexa.
  • Cada gravação altera dados (publica, aceita, salva). Redefina os dados entre as gravações.
  • Aguarde as salvaguardas automáticas e preenchimentos de IA em segundo plano antes do próximo passo, ou a UI salta.
  • A rolagem com roda pode recolher um cabeçalho colapsável e esconder o próximo botão. Prefira el.scrollIntoView() através de locator.evaluate.
  • Nunca deixe a página entrar em tela cheia do navegador: os quadros redimensionam e o vídeo corta para o canto superior esquerdo. Execute document.documentElement.requestFullscreen = undefined antes de um botão de tela cheia.

Dados de demonstração

  • Semeie exatamente o estado que a história precisa em um banco local, com pessoas verossímeis em example.com e números 555. Nunca um cliente real, nunca um banco de pré-visualização ou produção.
  • Tabelas de histórico, auditoria e eventos são somente anexação: cada regravação adiciona linhas que fazem a próxima gravação parecer encenada. Use um registro novo por gravação ou mantenha a história em ações que não deixam rastros.
  • Execute primeiro uma simulação: uma ramificação DRY=1 no fluxo clica em Cancelar em cada confirmação destrutiva. Capture quadros em algumas marcas de etapa, corrija localizadores e enquadramento, depois faça a única gravação real.
  • Após a gravação, verifique se o estado final do banco corresponde ao que as legendas ou narração afirmam.
  • Alterar os dados significa executar novamente o tts.mjs para as linhas que os descrevem.
  • O zsh não divide $VAR por palavras. Passe os tempos dos quadros para frames.mjs literalmente ou faça um loop sob o bash.

Vídeos tutoriais para clientes (onboarding, how-to)

  • Sempre narrados. Mantenha as legendas em frases curtas; a voz carrega os detalhes.
  • Uma etapa de email vai para o Gmail: mountGmail(page, { emailHtml, subject, sender, me }) de scripts/gmail-scene.mjs. Alimente com o email real renderizado do aplicativo. Ele reescreve target="_blank" para _top, pois um popup deixa a gravação esperando na página errada. Responder é embutido: #reply-btn, #reply-text, #send-btn. Passe mailbox: 'sam' para várias caixas de entrada (servidas em /sam e /sam/message). Uma resposta nunca chega ao aplicativo, então escreva seu efeito diretamente no banco local.
  • Espere por elementos, não por waitForURL: cadeias de redirecionamento de autenticação abortam navegações (ERR_ABORTED) e algumas páginas nunca disparam load. Dê a cliques de navegação { noWaitAfter: true }.
  • Após (re)iniciar um servidor de desenvolvimento, aqueça todas as páginas e rotas de API que a gravação acessa.
  • Ressemeie antes de cada gravação. Tokens de link mágico são de uso único, portanto uma tentativa falha os queima.

2. Verificar

node <skill-dir>/scripts/frames.mjs <video.mp4> 10 30 50 escreve frame-<t>.jpg ao lado do vídeo. Olhe para eles antes de entregar. Para narração, peça ao usuário para ouvir uma vez: quadros não podem capturar defeitos de áudio. Verifique se há dados reais de clientes (nomes, emails, telefones): fazer upload os publica para todos que podem ver o destino.

3. Entregar

Apenas o arquivo (padrão)

Imprima o caminho absoluto do .mp4 final. Pronto.

PR, issue ou comentário do GitHub (reproduz inline)

O GitHub só reproduz um vídeo inline a partir de uma URL https://github.com/user-attachments/assets/<uuid> em sua própria linha, e essa URL vem apenas do upload web do GitHub: gh, a API REST e arquivos commitados fornecem um link de download. Os uploads são privados para membros do repositório e nunca são commitados.

node <skill-dir>/scripts/attach-video.mjs <URL do PR ou issue do GitHub> <video.mp4>
  • Abre uma janela visível do Chromium. Se o login salvo expirou, mostra a página de login do GitHub: o usuário faz o login. Nunca peça a senha e nunca leia os cookies do navegador.
  • Faz upload através da caixa de comentário da página; o comentário nunca é postado. Imprime ATTACHMENT_URL https://github.com/user-attachments/assets/<uuid>.
  • Lembra o login em ~/.cache/screen-video/gh-profile. FORGET_SESSION=1 o deleta após o upload. Um upload concorrente espera pelo perfil (WAITING_FOR_PROFILE).

Coloque a URL sozinha em sua própria linha no corpo ou comentário (gh pr edit <n> --body-file <file>, gh issue comment <n> --body-file <file>). Para confirmar, verifique o body_html do PR por uma tag <video src=…private-user-images…>; um curl -sI na URL do ativo pode retornar 404 mesmo quando reproduz. Limite de tamanho: 10 MB em planos gratuitos, 100 MB em pagos.

Issue do Linear

O MCP do Linear faz upload diretamente; nenhum navegador é necessário.

  1. prepare_attachment_upload com issue, filename, contentType (video/mp4) e size (bytes exatos: stat -f%z <file> no macOS, stat -c%s no Linux).
  2. Dentro de 60 s, curl -X PUT --data-binary @<file> "<uploadRequest.url>" com todas as entradas uploadRequest.headers enviadas literalmente. Alterar a capitalização de um cabeçalho ou omitir um resulta em 403. Faça upload de um arquivo por vez.
  3. create_attachment_from_upload com issue e assetUrl.
  4. Para que seja reproduzido no tópico, use save_comment com ![walkthrough.mp4](<assetUrl>) mais uma legenda de uma linha. Abra a issue uma vez para confirmar que é renderizada como um player.
  5. Substituir um vídeo: faça upload do novo, edite o mesmo comentário (save_comment com id), então delete_attachment do antigo.

Slack

Com o MCP do Slack, após o usuário confirmar o canal (o compartilhamento não pode ser desfeito):

  1. slack_get_file_upload_url com filename e content_length (bytes exatos).
  2. URL='<upload_url>' && curl -X POST "$URL" -H 'Content-Type: video/mp4' --data-binary @<file>. Mantenha a URL byte por byte; um 401 significa que foi corrompida ou expirou, então obtenha uma nova.
  3. slack_complete_file_upload com file_id, channel_id (um id C…/D…, não um nome; procure com slack_search_channels), opcionalmente thread_ts e initial_comment.

Sem o MCP, forneça ao usuário o caminho do arquivo para arrastar.

Qualquer outro lugar

Entregue o caminho do arquivo e o tamanho; mencione scale: 1 / CRF=24 se o destino limitar o tamanho.

Habilidades Relacionadas