O que videolabs: criar um vídeo (estilo curso de terminal) faz?
Tudo reside em um único repositório, /opt/opus/videolabs (github.com/bigzaqui/videolabs, público: nunca commitar credenciais; execute scripts/check-secrets.sh antes de commitar). A fonte deste skill está em skills/videolabs_create-video/ nesse repositório, vinculada a ~/.claude/skills por scripts/setup.sh. Suas configurações (chaves ElevenLabs e Cloudflare, o host de renderização) estão no .env na raiz do repositório (ignorado pelo git), veja .env.example.
O repositório contém: um motor genérico (ferramentas Remotion + ElevenLabs, um npm ci) e uma pasta por laboratório em labs/. Usado para "kafka - em poucas palavras" (26 aulas / 18 min), "ceph - primeiros passos" (14 aulas / 11.6 min) e, anteriormente, "ceph em poucas palavras" (41 aulas / 30 min; seu script e visuais estão em reference/ como exemplo de cartões, comparação, medidor, árvore, incidente, estados e sites). Ambos os laboratórios atuais são práticos: todos os comandos foram executados primeiro em um laboratório real (veja "Laboratório real").
/opt/opus/videolabs/
engine/ ferramentas genéricas: engine/cli.mjs <laboratorio> <comando>, src/ (Remotion), scripts/
labs/<slug>/
lab.config.json entrada de catálogo (título, descrição, tags) + aparência (marca, logotipo, barra de status, nomes de ferramentas, corte de amostra)
script/script.md narração, uma cena por cabeçalho ###
visuals.ts o que cada cena mostra + substituições de dica
lab/ create-lab.sh, destroy-lab.sh, cap.sh, captures/, lab-meta.json
audio/raw/ saída ElevenLabs (paga; commitada no git)
out/ data/ public/voiceover/ gerados, ignorados pelo git
site/ scripts/publish-all.sh state/ o site e sua publicação (skill videolabs_publish)
script.md ──cli cenas──▶ data/scenes.json
└─ cli tts (timing de palavras ElevenLabs) ─▶ audio/raw ──pace──▶ public/voiceover/* + data/{durations,captions}.json
visuals.ts + engine/src/cues.ts (revelar = quando a palavra é dita) ─▶ Remotion ──cli render──▶ out/<slug>.mp4
cli player ─▶ out/player/{chapters.json,lab.json} ──videolabs_publish──▶ videolabs.pages.dev
Todos os comandos: node engine/cli.mjs <laboratorio> <cenas|tts|pace|dicas|stills|typecheck|studio|amostra|render|player>, mais list e new <slug> (README.md do repositório tem a tabela). A pasta do laboratório é a única coisa que difere entre cursos; nunca copie código do motor para dentro de um laboratório.
Fluxo de Trabalho (concorde o esboço e o script com o usuário; tudo depois disso é executado sem perguntar)
Autorização permanente do usuário: uma vez que o script esteja acordado, execute o tts pago do ElevenLabs, a auditoria de dicas, a renderização no host de renderização, o passo do player/pôster e o desmonte do laboratório sem pedir permissão em cada etapa. Relate os resultados e a nova duração depois. Só pare para perguntar quando algo estiver fora deste fluxo de trabalho (por exemplo, uma ação destrutiva em coisas que o laboratório não criou, ou publicação no site quando o usuário não pediu).
- Tópico prático? Construa e execute um laboratório real primeiro (veja "Laboratório real" abaixo). Nunca escreva a saída de comandos de memória: o primeiro rascunho do script ceph-primeiros-passos estava errado em dez lugares quando executado em um cluster real. Depois escreva o script a partir do que realmente aconteceu.
- Primeiro o script, depois o vídeo. Proponha um esboço (módulos → aulas, com o diagrama planejado por aula), obtenha concordância e então escreva
labs/<slug>/script/script.md. Mudar texto é muito mais barato que regenerar áudio. Marque na velocidade 1.0 com pausas ≈ 160 palavras por minuto: 1.500 palavras ≈ 9.5 min incluindo cartões de módulo; a 1.08 ≈ 168 ppm. Verifique a duração real com a linhaPaced … total X minlogo apósttse ajuste antes de renderizar. Para comandos específicos de versão, verifique o release atual (documentação e os downloads/imagens do projeto) primeiro. Formato (analisado porengine/scripts/build-scenes.mjs):
Termine a última aula de um módulo com "Resumo do módulo: …" — a palavra resumo dispara a janela de resumo.## Módulo 1 — Arquitetura Principal ### S02 · RADOS **Visual:** notas para o designer **Comandos:** `ceph -s` `ceph osd tree` (opcional, apenas na tela) **Narração:** Um parágrafo, escrito para ser falado. Soletre comandos como são falados ("ceph dash s"). node engine/cli.mjs new <slug>cria a estruturalabs/<slug>/(config, um script de três cenas, um visuals.ts); preenchalab.config.json(título, descrição, tags para o catálogo;brand,user,logo,statusbar,statusCmd,networkLabels,session,tools,samplepara a aparência) e executenode engine/cli.mjs <slug> scenes.- Visuais em
labs/<slug>/visuals.ts(tipos emengine/src/types.ts,colorsde@engine/theme), uma entrada por id de cena. Tipos de diagrama (engine/src/components/Diagrams.tsx):topology: hosts (nome, ip, chips de daemon, discos/réplicas) em uma ou duas linhas de rede, uma linha de status opcional (rótulostatusCmd, padrão da config), eflows(blocos se movendo entre hosts, decueatéuntil). Qualquer host/daemon/disco/status pode ter umcue(aparece quando falado) echanges: [{cue, state, label}](discoempty|osd|leader|idle|down|destroyed|new, hostok|spare|down|maint). Elementos semcuesão visíveis desde o início. Construa o estado do laboratório com helpers (host(n),built(n)) para que o diagrama cresça aula por aula.session: um shell realista; cadacmdse digita quando falado, então suas linhasoutaparecem; mantém as linhas mais recentes.hostdefine o prompt completo de uma linha (padrãosession.promptda config). Umatopologycompacta opcional acima dela. A barra de comandos na parte inferior fica oculta para cenas de sessão. Outros tipos:title(cartão fastfetch;user,rowsextras),layers,cards,flow,tree,compare,gauge,terminal(saída de comando com anotações),incident(sintoma/verificação/ação),checklist,states,sites; opcionaistakeawayecommandCuespor cena. Use saída real delab/captures/para cadaoutesymptom.
- Revise os visuais antes de pagar pelo áudio:
node engine/cli.mjs <slug> stills(um quadro por cena a 60%;S04,S11 --at=12.5para um momento) e monte em folhas de contato 2×2 com ffmpegxstackpara examiná-los. Verifique estouro, quebra de linha, layouts vazios. - Voz:
.envna raiz do repositório comELEVENLABS_API_KEY/ELEVENLABS_VOICE_ID, depoisnode engine/cli.mjs <slug> tts(execute diretamente, sem necessidade de aprovação; pula existentes;--only=S03,S04 --forcepara refazer apenas as cenas cuja narração mudou, o que também salva créditos). Ele executapaceautomaticamente. Informe ao usuário a nova duração quando o script crescer. Commitlabs/<slug>/audio/(saída paga). - Auditoria de sincronia:
node engine/cli.mjs <slug> cues(após qualquer alteração em um comando, altere também sua entradacommandCues: um chip paradmesgainda com dica em "smartctl" é uma incompatibilidade silenciosa) — lista a palavra que revela cada elemento e cada linha de comando (MISS= espalhado entre vizinhos). Corrija correspondências erradas com substituições de dica (abaixo). Reexecute após qualquer alteração na narração ou na voz. - Pré-visualização:
node engine/cli.mjs <slug> sample(≈1 min no servidor de renderização), verifique quadros comffmpeg -ss <t> -frames:v 1,volumedetectpara níveis (sem corte: máx < -1 dB). - Renderize o curso completo (remoto, abaixo), depois
node engine/cli.mjs <slug> player, faça o pôsterlabs/<slug>/out/spot.jpg, destrua as VMs do laboratório e publique todo o site com o skill videolabs_publish.
Regras de design aprovadas pelo usuário
- Aparência: Área de trabalho Omarchy/Hyprland, paleta Tokyo Night (
src/theme.ts; nomes semânticos comorados,mon,cephfssão apenas slots da paleta). Barra Waybar com módulos como áreas de trabalho 0–7, um status (HEALTH_WARNno módulo de solução de problemas), contador de aula07/41. Uma janela lado a lado, borda quadrada de 2px, sem raio/brilho/gradiente. Cada aula abre com um prompt digitado robbyrussell➜ modulo-1-arquitetura-principal git:(s03) cat os-daemons.md, depois um# Cabeçalho. Notas renderizadas como// comentários, comandos como linhas❯ cmd, resumos como janela flutuantecat resumo-sre.md, divisores de módulo comocd modulo-N-…+ título grande, abertura como fastfetch. Tudo JetBrains Mono; Inter apenas para legendas. Isso substituiu uma aparência "genérica" de gradiente escuro que o usuário não gostava. - Sincronia de revelação: nunca espaçar por tempo. Cada elemento aparece quando o narrador diz seu nome pela primeira vez (
engine/src/cues.ts: token significativo mais longo, com stemização de prefixo, pesquisado para frente a partir da dica anterior). Substituições novisuals.tsdo laboratório:cue: "=gerentes"palavra falada exata (hífens/pontuação ignorados:"=write-ahead"),cue: null= nenhuma palavra, interpolar.pointCues(lados de comparação),noteCue(árvore),linkCue(sites),cues(emblemas de título),commandCues(linhas de comando, por cena).- A ordem dos rótulos em
cueLabels()deve coincidir com a ordem de índices que o renderizador usa emengine/src/components/Visuals.tsx(topology/session usam traversal por chave emtopologyKeys/sessionKeys). - Um cursor para frente percorre todos os rótulos de uma cena, então um rótulo posterior reutilizando uma palavra comum ("três", "recuperação", "adicionar") pode cair em uma ocorrência posterior. Prefira uma palavra que ocorra uma vez perto do momento ("=repetir", "=começa"), e sempre leia a auditoria de
cues.
- Legendas: frases simples, quebradas no final de frases/períodos, uma linha ≤ 64 caracteres, sem destaque karaoke palavra por palavra ("cansativo").
- Respiração:
engine/scripts/pace.mjsinsere 250 ms após. ? !e 120 ms após: ;no áudio bruto do ElevenLabs e desloca os timings das palavras. Os pontos de corte vêm do ffmpegsilencedetect(estrito -38 dB/40 ms, depois -30 dB/20 ms), ou então a janela mais silenciosa de 5 ms, com fades de 8 ms — nunca apenas dos limites das palavras do ElevenLabs (que caem dentro das palavras). AjusteSENTENCE_PAUSE/CLAUSE_PAUSE, reexecutenode engine/cli.mjs <slug> pace(grátis; áudio bruto é mantido emaudio/raw/). O ritmo depende da versão do ffmpeg (7.1.5 e 8.0.1 deram 17.9 e 17.8 min para o mesmo áudio Kafka): faça o ritmo onde renderiza, ou copiedata/epublic/voiceover/para o host de renderização. - Transições: crossfades de 15 quadros via
@remotion/transitionsTransitionSeries; elas sobrepõem apenas o lead-in silencioso (0.4 s) / tail (0.8 s).engine/src/timeline.tssubtrai sobreposições;engine/scripts/player.mjsestills.mjsespelham essas constantes — mantenha-as em sincronia. - Voz escolhida pelo usuário: ElevenLabs "Mark"
v3p1kjzUvro6S76qmYmH(Biblioteca de Vozes; funciona para TTS emboraGET /v1/voices/{id}retorne 404),eleven_multilingual_v2, velocidade 1.08, endpointwith-timestamps,previous_text/next_textpara continuidade. Sempre verifique um id de voz com uma chamada TTS de 1 frase antes de uma execução completa (um id anterior em.envestava truncado).
Configurações por curso (labs//lab.config.json)
Os workspaces na barra Waybar e as contagens de módulo/aula no fastfetch seguem as cenas automaticamente. Todo o resto específico do curso são dados na config, nunca código: brand (nome Waybar), user (prompt no cartão de título), logo (cinco linhas de letras em bloco), statusbar (texto ok/warn, texto right, warnModule = o módulo que mostra o aviso), statusCmd e networkLabels (padrões de cenas de topologia), session.prompt/session.title (sessões de terminal), tools (palavras de comando ignoradas ao dar dica automaticamente a uma linha de comando), sample (ids de cena do corte de Amostra), mais title, description, tags para o catálogo. Um novo laboratório com aparência diferente não precisa de alteração no motor; um novo tipo de diagrama ou estado precisa (engine/src/types.ts, componentes, cues.ts) e então beneficia todos os laboratórios.
Práticas do Remotion aplicadas (dos skills remotion-*)
Fontes via @remotion/google-fonts (nunca confie em fontes do sistema); <Audio> de @remotion/media com from; TransitionSeries; calculateMetadata dimensiona a composição a partir das durações de áudio; anime com interpolate/Easing.bezier e translate (sem transições CSS). Não adotado propositalmente: Interactive.create/composições conectadas (útil apenas para edição manual no Studio), mínimos grandes de texto para vídeo social, efeitos sonoros de meme.
Renderização
VIDEOLABS_RENDER_HOST(alvo ssh do servidor de renderização e laboratório, chaves já configuradas) vem do.envna raiz do repositório: executeset -a; . /opt/opus/videolabs/.env; set +aantes dos comandos abaixo, e escreva essa variável, não um endereço literal, em qualquer coisa que commitar. O mesmo host executa as VMs do laboratório.TMPDIR=/var/tmpé definido pelo CLI:/tmpé um tmpfs RAM pequeno nesses hosts e uma mixagem de áudio longa falha com "No space left on device" bem no final.- Máquina local (4 vCPU) ≈ 45 min por 30 min de vídeo. Servidor de renderização remoto $VIDEOLABS_RENDER_HOST (Ubuntu 26.04, 24 threads, 31 GB RAM, RTX 3090 Ti, KVM; Node 22, ffmpeg, rsync e as bibliotecas Chromium do apt) renderiza o curso ceph de 11.6 min em 3 min e o curso kafka de 18 min em 5 min com concorrência 12–16. O repositório reside em
/opt/opus/videolabsem ambas as máquinas. Fluxo:(Para manter o timing idêntico ao que você revisou, sincronizersync -az --delete --exclude node_modules --exclude out --exclude .env --exclude .git --exclude site --exclude state ./ $VIDEOLABS_RENDER_HOST:/opt/opus/videolabs/ # de /opt/opus/videolabs ssh $VIDEOLABS_RENDER_HOST 'cd /opt/opus/videolabs/engine && npm ci && cd .. && node engine/cli.mjs <slug> scenes && node engine/cli.mjs <slug> pace && node engine/cli.mjs <slug> render --concurrency=12 --log=error' rsync -a $VIDEOLABS_RENDER_HOST:/opt/opus/videolabs/labs/<slug>/out/<slug>.mp4 labs/<slug>/out/ && node engine/cli.mjs <slug> playerlabs/<slug>/dataepublic/voiceoverem vez de executarscenes/paceno servidor.) Nunca copie.envpara lá. Execute renderizações longas detached (nohup bash script &iniciado comssh -f -n) e aguarde com um Monitor na linharealdotime; não grep o log por "error" (a linha de comando tem--log=error). Se o código do Waybar/diagrama mudar, verifique ambos os laboratórios (stills), pois eles o compartilham. - Verificando uma alteração no motor: renderize o corte de Amostra com o código antigo e o novo e compare
ffmpeg -i a.mp4 -i b.mp4 -lavfi ssim -f null -;scenes/durations/captions.jsonregenerados e a auditoriacuesdevem
… [truncado para tradução]