Instrutor de Legendas para Demonstrações de Produtos
Problema
As demonstrações de produtos gravadas em tela falham com seu público por três razões que nada têm a ver com o produto: o texto da interface está no idioma errado, é muito pequeno para ler, e o vídeo geralmente não tem narração alguma (gravações silenciosas). O espectador vê cliques acontecendo, mas não consegue entender o porquê. As legendas resolvem isso — mas apenas se forem estilizadas para serem legíveis sobre uma interface ocupada, colocadas onde não cobrem o que está sendo demonstrado e temporizadas para nunca serem cortadas por uma transição.
Esta skill é o pipeline completo, incluindo os modos de falha não óbvios que custam horas quando descobertos da maneira difícil.
Quando usar
- "Adicione legendas a este vídeo de demonstração" / "legende esta gravação para a nossa equipe coreana"
- Uma gravação de tela silenciosa (
.mp4, sem fluxo de áudio, ou áudio que é apenas música de fundo) onde a história deve ser contada pelo próprio texto da legenda - Uma demonstração de produto apenas em inglês sendo mostrada para um público não falante de inglês
- Legendas já existem, mas ficam centralizadas na parte inferior e cobrem o painel da demonstração — precisando de posicionamento que siga o foco
- Gerar um roteiro de leitura para um apresentador que falará sobre o vídeo (veja "Uso complementar")
Não use esta skill para: dublagem com voz sintetizada (isso é um pipeline TTS) ou extração de legendas que já existem em uma plataforma.
Instalação (um comando)
scripts/bootstrap.sh --dry-run # apenas inspeciona: imprime exatamente o que está faltando
scripts/bootstrap.sh # instala o que está faltando (pergunta uma vez)
scripts/bootstrap.sh --yes --lang ja # automático; também prepara fontes japonesas
bootstrap.sh é idempotente — não faz nada em uma máquina que já está pronta. Ele detecta a plataforma e escolhe o instalador correto (Homebrew / apt / dnf / pacman / winget / scoop), e cai para baixar um ffmpeg estático em ~/.local/bin quando nenhum gerenciador de pacotes está disponível. Execute-o como a primeira ação de qualquer sessão que queimará legendas; skills não têm gancho de instalação, então este script é o instalador.
Pré-requisitos
Execute a verificação integrada primeiro — ela inspeciona a máquina e informa exatamente o que está faltando:
scripts/doctor.sh "Hiragino Sans GB" # passe a família CJK que você pretende usar
O que é verificado e o comando de instalação quando algo está faltando:
| Requisito | Por quê | Instalação |
|---|---|---|
ffmpeg compilado com libass | queima as legendas .ass (-vf ass=...) | macOS brew install ffmpeg; Windows: build gyan.dev completo; Debian apt install ffmpeg |
--enable-fontconfig na compilação do ffmpeg | resolve fontes pelo nome da família; sem ele você obtém uma face de fallback silenciosa | as mesmas compilações acima o incluem |
| uma família de fontes CJK que o sistema pode resolver | o texto da legenda em si | macOS: Hiragino Sans GB (pré-instalada) · Windows: Microsoft YaHei (pré-instalada) · Linux: apt install fonts-noto-cjk |
python3 (opcional) | apenas export_cues.py; biblioteca padrão, nenhum pip install | macOS: xcode-select --install; Linux: apt install python3 |
bash, zip (opcional) | scripts e empacotamento para distribuição | pré-instalados em todos os lugares |
Sem Node, sem ImageMagick, sem pacotes pip, sem chaves de API. Um modelo com capacidade de visão (o próprio agente) lê os quadros — isso não é algo que você instala.
Duas observações de ambiente que pegam:
- Coloque
ffmpegnoPATH, ou exporte sua localização para os scripts:FFMPEG=/caminho/para/ffmpeg scripts/burn.sh .... - Windows: libass resolve fontes através de fontconfig/DirectWrite dependendo da compilação. Se as legendas renderizarem na face de tipo errada, instale fontconfig (
scoop install fontconfigou MSYS2) e verifique novamente comfc-match "Microsoft YaHei:bold".
Pipeline
Trabalhe de cima para baixo. Não pule a Fase 2 — toda fase posterior depende do conhecimento da geometria da interface.
Fase 1 — Inventário (2 minutos)
scripts/probe.sh INPUT.mp4
Registre: duração, resolução, fps, bitrate, se existe um fluxo de áudio. Execute a verificação de áudio dentro do script (volume médio + mapa de silêncio).
Decisão a partir da verificação de áudio:
| Achado de áudio | Tipo de legenda |
|---|---|
| Nenhum fluxo de áudio | Legendas de narração — você escreve a história ("o que está acontecendo / por que importa") |
| Áudio presente, mas sem fala (apenas música) | Igual ao anterior. Confirme transcrevendo uma fatia de 10s: uma ferramenta ASR que retorna vazia ou quase vazia em áudio alto significa música, não voz |
| Fala presente | Legendas de diálogo — transcreva com timestamps primeiro, depois limpe; não invente texto |
Fase 2 — Análise visual e geometria (a fase que as pessoas pulam)
Extraia quadros a 2 fps (um clipe de 60s → ~120 quadros; revise cada 4º quadro primeiro para o arco, depois amplie o texto):
scripts/split_frames.sh INPUT.mp4 frames/ 2
Construa três artefatos antes de escrever uma única legenda:
-
Folha de batida —
| tempo | o que mudou na tela | texto na tela (verbatim) |. Uma "batida" é qualquer mudança: uma notificação aparece, um painel desliza, um botão é clicado, uma rolagem acontece, uma troca de página, uma transição. Anote os tipos de transição (flash branco, corte, preto) — as legendas não devem cruzá-los. -
Texto da interface, verbatim, lido da resolução original. Quadros de pré-visualização reduzidos borram o texto pequeno da interface e você o transcreverá errado. Corte do arquivo original:
ffmpeg -i IN.mp4 -ss 12.5 -frames:v 1 -vf "crop=W:H:X:Y,scale=2*iw:-1" out.jpg(-ssapós-ipara extração precisa do quadro; veja Armadilhas.) -
Mapa de geometria — os limites em pixels do painel "ao vivo" (a coisa sendo demonstrada) em pixels de origem. Meça, não olhe:
scripts/find_edge.sh INPUT.mp4 30 2880 300 # t=30s, faixa a partir de x=2880, 300px de larguraEntão leia a imagem da faixa retornada e anote onde a borda esquerda do painel cai dentro da faixa; adicione esse deslocamento ao x da faixa para obter a borda absoluta. Repita para outras cenas se o layout mudar (por exemplo, um painel de bate-papo abre mais tarde).
Regra que segue do mapa de geometria: a legenda vive na região livre adjacente ao painel de demonstração, nunca em cima dele. Veja a Fase 5.
Fase 3 — Escreva o texto da legenda
Duas regras governam tudo:
- Uma ideia por legenda. Máximo de 2 a 3 linhas curtas. Uma legenda é uma sentença, não um parágrafo.
- Orçamento de velocidade de leitura. CJK: 3,5–5,5 caracteres por segundo é confortável; acima de 6 é uma correria; abaixo de 3 parece morto. Latim: 12–18 caracteres por segundo. Calcule, não adivinhe.
scripts/export_cues.pyimprime a densidade por legenda depois que você tem um arquivo.ass— itere contra esse número.
Regras de temporização da legenda:
- Inicie a legenda quando a batida visual ocorrer, não antes. Se uma notificação deslizar em 1,0s, a legenda começa em 1,0s.
- Termine as legendas antes de uma transição (flash branco/corte), deixando pelo menos 0,8s de silêncio através dela. Uma legenda que atravessa uma transição parece um problema técnico.
- Deixe lacunas reais. Três a cinco segundos em branco por minuto mantém o vídeo respirando; legendas de ponta a ponta cansam o espectador.
- Para legendas de narração, coloque o motivo na legenda, não a mecânica: "o agente agiu por conta própria" é melhor que "uma notificação apareceu no canto superior direito".
Pare aqui e obtenha a aprovação do texto. Texto aprovado → estilização. Texto rejeitado após a estilização custa uma segunda renderização por nada.
Fase 3b — Escolha o idioma da legenda
O pipeline é independente de idioma, mas três coisas devem ser decididas por idioma antes da estilização.
1. Família de fontes — escolha uma que a plataforma possa resolver (verifique com scripts/doctor.sh <idioma>):
| Idioma da legenda | macOS | Windows | Linux |
|---|---|---|---|
| Chinês (zh) | Hiragino Sans GB / PingFang SC | Microsoft YaHei | Noto Sans CJK SC |
| Japonês (ja) | Hiragino Sans | Yu Gothic / Meiryo | Noto Sans CJK JP |
| Coreano (ko) | Apple SD Gothic Neo | Malgun Gothic | Noto Sans CJK KR |
| Latim (en/fr/de/es…) | Helvetica Neue | Segoe UI | Inter / DejaVu Sans |
2. Velocidade de leitura — o orçamento depende do script; scripts/export_cues.py seleciona o correto automaticamente (--lang substitui) e sinaliza legendas que o excedem:
| Script | Confortável | Sinalizar como muito rápido |
|---|---|---|
| Han / Kana (zh, ja) | 3,5–5,5 chars/s | > 6,0 |
| Hangul (ko) | 4,0–6,0 chars/s | > 6,5 |
| Latim | 12–18 chars/s | > 19 |
3. Quebra de linha — Chinês e japonês não têm espaços entre palavras: quebre na pontuação (U+FF0C, U+3001, U+3002, U+FF1A) e nunca divida um nome de produto. Coreano e latim quebram nos espaços; mantenha uma partícula coreana anexada ao seu substantivo. Se uma linha não couber, re-embrulhe antes de encolher a fonte — uma face de tipo menor perde mais legibilidade do que uma linha extra.
4. Largura da linha por script — a largura do glifo não é a mesma entre scripts, então o mesmo Fontsize dá comprimentos de linha diferentes. Orçamente as linhas como floor(disponivel_px / (fontsize * k)), onde k é aproximadamente 1,0 para Han e 1,05–1,1 para Hangul e Kana (eles definem mais largos), e 0,5 para Latim em média (varia com a palavra).
Medido em 4K com Fontsize 165: 13 caracteres Hangul ≈ 2.150 px (seguro em um slot de 2.450 px), enquanto 18 caracteres ≈ 2.970 px — isso transborda e empurra a linha para fora da borda esquerda do quadro. Quando uma linha não couber, re-embrulhe-a; não encolha a fonte para fazê-la caber.
Legendas bilíngues (original + tradução): apenas quando ambas as linhas são curtas. Duas linhas de idioma dobram a carga de leitura e, em uma demonstração, competem com a interface. Prefira um idioma por versão do arquivo, a menos que o cliente peça o contrário; quando for necessário, empilhe o original em cima e a tradução abaixo, e orçamente todo o bloco contra a velocidade de leitura da tradução.
Scripts da direita para a esquerda (árabe, hebraico): libass suporta RTL, mas a qualidade da modelagem depende do harfbuzz da compilação. Renderize um quadro de pré-visualização antes de confirmar — verifique se as letras se juntam corretamente e se a âncora de alinhamento ainda faz sentido.
O peso da fonte difere por script. Famílias japonesas e coreanas geralmente renderizam visualmente maiores do que uma família chinesa no mesmo Fontsize; verifique com um quadro de pré-visualização e ajuste a olho, não por regra.
Fase 4 — Estilo (o brilho de duas camadas)
Um único estilo ASS não pode produzir "texto branco com um contorno colorido grosso e um halo colorido suave" — Outline desenha apenas um anel. Use dois eventos por legenda em duas camadas:
| Camada | Preenchimento | Contorno | Desfoque | Propósito |
|---|---|---|---|---|
| 0 | transparente (&HFF000000) | largo, roxo semitransparente | \blur 20–30 | o halo |
| 1 | branco | mais estreito, roxo sólido | nenhum / \blur1 | o texto legível |
Ponto de partida (veja assets/caption-template.ass). As cores são &HAABBGGRR — alfa 00 é opaco, FF é totalmente transparente e a ordem dos bytes é BGR:
roxo RGB(124,46,240) → &H00F02E7C
halo, 35% opaco → &H55F02E7C
Escala por PlayRes, não por gosto: uma renderização 4K usou fontsize 180 / outline 16 / halo 28 / blur 24; a mesma aparência em 1080p é 80 / 7 / 12 / 12. Defina PlayResX/Y para a resolução de saída e mantenha ScaledBorderAndShadow: yes.
Fase 5 — Posicionamento que segue o foco
Centralizado na parte inferior é errado para demonstrações: a parte inferior geralmente é a interface, e o painel de demonstração é a única coisa que deve permanecer visível. Ancore cada legenda na região livre adjacente a o que está sendo mostrado:
| Foco desta legenda | Âncora | Por quê |
|---|---|---|
| Um painel lateral / painel de bate-papo está aberto | \an6\pos(<painel_esquerda - 40>, <painel_meio_vertical>) | borda direita abraça o painel, o bloco cresce para a esquerda, mudanças de largura não precisam de recálculo |
| Um toast/notificação no canto superior direito | \an3\pos(<toast_direita>, <toast_fundo + 80>) | âncora no canto inferior direito coloca o bloco logo abaixo do toast |
| Página de largura total, sem painel | \an5\pos(<centro_visual_x>, <y entre dois blocos de conteúdo>) | centralizado, mas colocado em uma lacuna medida — não no centro geométrico |
| Cartão de título com tipo grande embutido | \an5\pos(centro_x, ABAIXO do título) | o centro geométrico colide com o próprio título do vídeo |
Calcule a largura máxima da linha antes de escrever: max_line_px ≈ fontsize × caracteres. Se uma linha exceder a região livre, re-embrulhe-a (nunca encolha a fonte apenas para uma legenda) — divida na pontuação e mantenha os nomes dos produtos intactos.
Para blocos ancorados à direita, o derramamento de algumas dezenas de pixels sobre um fundo plano é aceitável e parece intencional; derramamento sobre o painel de demonstração não é.
Mantenha a âncora longe da borda do quadro. O contorno e o halo estendem-se além da caixa do glifo em aproximadamente outline + blur pixels (em 4K com outline 15 / blur 22, isso é ~40 px, mais anti-aliasing). Uma âncora \an3 estacionada a 50 px da borda direita, portanto, tem seu halo cortado e parece uma palavra quebrada. Estacione âncoras pelo menos 150 px dentro do quadro e verifique o resultado em um quadro de pré-visualização — o corte é invisível no arquivo .ass e óbvio na tela.
Fase 6 — Queima
scripts/burn.sh INPUT.mp4 subs.ass OUT.mp4 [crf]
Isso é -i IN -vf "ass=subs.ass" -c:v libx264 -crf 17~18 -pix_fmt yuv420p -movflags +faststart, mantendo a trilha de áudio original. Legendas fixas são o padrão correto para demonstrações — elas sobrevivem a qualquer player, upload e compartilhamento de tela; envie um .ass/.srt junto apenas se solicitado.
Fase 7 — Verificação
Renderize imagens baratas primeiro e, em seguida, verifique o arquivo codificado:
scripts/preview_frame.sh INPUT.mp4 subs.ass 16.0 /tmp/check1.jpg
Verifique cada quadro para: texto sobre o painel de demonstração, texto fora do quadro, quebra de linha inesperada, uma legenda que deveria ter terminado, halo que se transformou em mingau sobre um fundo brilhante. Em seguida, re-extraia 3 quadros do arquivo finalizado (as legendas estão gravadas, a qualidade é final) e confirme novamente.
Armadilhas (cada uma custou tempo real para encontrar)
-ssantes de-isilencia o filtroass. A busca de entrada redefine os timestamps para zero, então o filtro renderiza o estado da legenda em t≈0 para cada quadro. Resultado: o vídeo queima sem legendas, e ffmpeg não imprime nenhum erro. Sempre coloque-ssapós-i(busca de saída), ou adicione-copyts. Os scripts incluídos já fazem isso.- As coordenadas do quadro de pré-visualização mentem. Um quadro dimensionado para 1280×720, mas exibido para você em...
...
[truncado para tradução]