/dubbing
Uma skill que faz dublagem automática de vídeos via API Perso AI Dubbing.
Regras principais (obrigatório seguir)
- Apenas o worker vê a chave bruta. Nunca abra a chave com
Read, a exiba ou a passe como argumento de linha de comando. Os workers a enviam apenas no cabeçalhoXP-API-KEY. - Um comando = o trabalho inteiro.
scripts/dubbing.mjslida com upload · split · tradução · merge · salvamento por conta própria. Nunca executeprepare_input.mjsouprobe_split.mjsantes dele — são ferramentas de depuração, eprobe_split.mjsfaz um upload real (o vídeo seria enviado duas vezes). - Sempre execute
dubbing.mjsem segundo plano. Os trabalhos levam de minutos a horas; um timeout do shell em primeiro plano mata a execução no meio. - Idiomas não especificados → source
auto, targeten. - Vários idiomas vão em um único comando (
--target en,zh,ja) — upload/split acontece uma vez e é reutilizado por idioma. Nunca execute uma vez por idioma (re-envia a origem cada vez). - Transmita o progresso fielmente. Exiba as linhas
[progress]do stdout do worker para o chat, textualmente ou resumidas — e não anuncie etapas que não apareceram (splitting/merging existem apenas para vídeos acima do limite). Os logs detalhados de stderr (com indentação) não precisam ser repassados. - Mantenha o usuário informado enquanto executa. Dublagem, lip-sync e separação de áudio imprimem uma pulsação
[status](estágio + contagens de itens, ex.:ja 4/6 · en 1/6ouseparating — file 2/5, com uma dicanext check ~Nm), e cada item finalizado é transmitido como uma linha[progress]. Verifique a saída em segundo plano cerca de 3 minutos após o lançamento, depois no intervalo indicado (10 min para mídia ≤10 min, 30 min para mais longa): transmita qualquer nova linha[progress]imediatamente, caso contrário, publique a linha[status]mais recente como uma atualização de uma linha. Nunca invente porcentagens, tempo decorrido ou ETAs — transmita apenas o que as linhas dizem. Paradas que precisam do usuário ([space-select],[split-confirm],[free-limit],[resume-check],[credit-check], crédito insuficiente, conclusão) encerram o processo, então aparecem sem necessidade de polling. - Nomeie seu runtime para telemetria (silenciosamente). Adicione
--host <claude-code|codex|cursor|antigravity|gemini>ao comando do worker, nomeando o agente que você está executando, se puder determinar. Ele apenas marca a telemetria de uso — nunca o exiba para o usuário e nunca pergunte sobre ele. Omita-o se não tiver certeza. - Formatos não suportados são ignorados automaticamente e o restante continua processando — transmita o aviso de ignorado.
- Uma pausa não é uma falha. Uma linha
[space-select],[split-confirm],[free-limit],[credit-check]ou[resume-check]significa que a execução parou para perguntar algo — ela sai com código 0 (então nunca parece um erro), mas o trabalho não está completo. Sempre que uma dessas linhas estiver presente, nunca relate a execução como concluída: transmita-a e aja sobre ela. As linhas são o sinal, não o código de saída.
Configuração (preguiçosa — sem etapa prévia)
- Chave da API — não é uma etapa para executar primeiro. O worker se autoprotege no momento em que um comando que precisa da API Perso é executado: na primeira execução, ele abre uma página do navegador (faça login, um clique — a chave é armazenada criptografada) ou imprime instruções baseadas em arquivo se nenhum navegador puder abrir — transmita-as ao usuário. Em uma máquina headless/SSH, o worker pula o fluxo do navegador completamente e começa com registro de arquivo/
--import(a página do navegador só pode entregar uma chave para a máquina em que é executada) — transmita essas instruções da mesma forma. Nunca acione o registro de chave você mesmo: o worker iniciaconnect.mjs/resolve_key.mjs --watchquando necessário, então não os execute, e não executeresolve_key.mjs --checkcomo uma etapa prévia (só se o usuário perguntar explicitamente se uma chave está registrada — uma chave ausente não é um erro). Nunca cole a chave no chat. Obtenha uma chave: https://developers.perso.ai/api-keys - ffmpeg/ffprobe — instalado automaticamente apenas quando um vídeo excede o limite do plano e precisa ser dividido (aprove se a permissão for solicitada). Verificação manual:
node scripts/check_deps.mjs.
Execução
Colete a entrada (caminho local ou URL — pergunte novamente se estiver faltando) e execute em segundo plano — o worker faz a proteção da chave sozinho na primeira necessidade; apenas transmita suas instruções impressas se o registro iniciar:
- Único:
node scripts/dubbing.mjs "<arquivo|URL>" [--source auto] [--target en] [--space "nome do espaço"] [--out resultado.mp4] [--lipsync] [--no-save] - Vários idiomas / várias entradas (uma ou mais entradas; URLs e arquivos podem ser misturados):
node scripts/dubbing.mjs "<URL>" "<arquivo>" --target en,ja— uma saída por entrada × idioma, salva ao lado de cada origem (--out <pasta>as coleta). - Pasta (lote):
node scripts/dubbing.mjs "<pasta>" [--target en,zh] [--recursive] [--out pasta-de-saida]
Códigos de idioma — --target aceita um código de idioma (en, ja) ou uma tag regional. Variantes regionais existem apenas para Inglês (en = US padrão, en-GB = UK), Português (pt = Brasil padrão, pt-PT = Portugal) e Espanhol (es = México padrão, es-ES = Espanha); en-US, pt-BR e es-MX são grafias aceitas desses padrões. Cada outro idioma tem uma única variante, e --source só precisa do idioma (uma tag regional nele é tratada como o idioma simples).
- Região nomeada na solicitação → use essa tag exata. Uma região sem variante (ex.: inglês australiano) → informe ao usuário que apenas as opções listadas existem e deixe-o escolher antes de executar.
- Tokens de duas letras são códigos de idioma, não países:
uksignifica Ucraniano — inglês britânico éen-GB. Mesma armadilha paraca(Catalão),be(Bielorrusso),eu(Basco). Se a redação do usuário puder significar um ou outro, pergunte qual ele quer antes de executar. - Idioma nomeado sem região → a variante padrão. Para esses três idiomas, nomeie o padrão aplicado na nota de entrega (ex.: "Espanhol (México) — use
es-ESpara Espanha").
Local de salvamento — --out aceita uma pasta (um diretório existente ou um caminho terminando em /) que coleta os resultados dentro dela, ou um caminho de arquivo para uma única entrada (a extensão real é anexada se o caminho não tiver nenhuma). O padrão é ao lado de cada arquivo de origem; não pergunte. No primeiro trabalho da sessão, indique-o na linha de início (ex.: "Os resultados serão salvos ao lado dos originais — diga-me agora se quiser uma pasta diferente") e comece imediatamente, sem esperar por uma resposta. Se o usuário nomear uma pasta — com esta solicitação ou a qualquer momento depois nesta sessão — execute com --out "<pasta>" e continue usando essa pasta para cada trabalho posterior até que ele a altere. Se ele redirecionar após uma execução já iniciada, mova os arquivos concluídos para lá quando a execução terminar. Exceção: quando cada entrada for uma URL (sem pasta de origem local), pergunte onde salvar antes de começar.
Seleção de espaço — com vários workspaces, o worker para antes do upload, imprime linhas [space-select] (nome | (plano) | créditos restantes) e para. Mostre ao usuário APENAS essas opções (sem números internos), pergunte qual delas e execute novamente com --space "<nome do espaço>". Um espaço → nenhuma pergunta; PERSO_SPACE_SEQ o fixa.
Confirmação de split — se a entrada exceder o limite de comprimento ou tamanho e precisar ser automaticamente dividida e mesclada (dublagem, dub+lip-sync, ou separação de áudio), o worker para e imprime linhas [split-confirm]. Transmita-as e pergunte ao usuário: excede o limite de comprimento/tamanho, então precisa de split automático → processamento → merge, que pode resultar menos polido do que dividir por conta própria — prosseguir automaticamente? Em caso afirmativo, execute novamente o mesmo comando com --allow-split (nada é cobrado até este ponto, então reexecutar é gratuito). Execuções em lote: --allow-split autoriza cada split na execução.
Não salvar (apenas servidor) — quando o usuário quiser o vídeo dublado, mas não salvo como arquivo local, adicione --no-save: o worker deixa o resultado no workspace Perso do usuário e pula o download, imprimindo Mantido no servidor, não salvo: … → projeto <seq> (a linha [project-ref] ainda é emitida, então a dublagem pode ser lip-syncada ou recuperada depois). Apenas vídeos únicos/não divididos — o arquivo mesclado de um vídeo dividido precisa de um download local, então é salvo normalmente e o worker informa isso. Não pode ser combinado com --lipsync (o vídeo com lip-sync deve ser baixado).
Plano gratuito (resultados permanecem no Perso) — um workspace Gratuito pode dublar, fazer lip-sync e separar normalmente, mas o servidor recusa todo download, então o worker nunca salva um arquivo lá. Ele imprime Plano gratuito — <nome>: … pronto no Perso → <url> (downloads precisam de um plano pago) — transmita esse link como o resultado entregue: a execução está completa, nada falhou e não há nada para retomar. Se o usuário quiser arquivos reais, isso requer um plano pago — ofereça o caminho de upgrade (veja Upgrade de plano e créditos), nunca uma reexecução.
Pausas [free-limit] do plano gratuito — duas delas, ambas antes de qualquer cobrança:
- Pré-visualização de 30 segundos. Uma entrada de dublagem/lip-sync com mais de 30 s para com linhas
[free-limit]: no Gratuito, apenas os primeiros 30 segundos seriam dublados, o resto omitido. Transmita a pergunta e, em caso afirmativo, execute novamente o mesmo comando com--allow-preview(o worker corta os primeiros 30 s localmente e envia apenas isso). Um link de plataforma importado pelo Perso não pode ser cortado — aí a resposta é um arquivo local, um clipe mais curto ou um upgrade. - Acima do limite do plano. Mídia acima do limite de comprimento/tamanho do plano não recebe oferta de split no Gratuito (
--allow-splitnão se aplica): as linhas[free-limit]dizem que um arquivo mais curto ou um upgrade é a única saída. Transmita-as e pare.
Enquanto executa (para explicar a espera):
- Split: o arquivo inteiro é enviado primeiro; apenas uma rejeição de limite do plano instala o ffmpeg e divide sem perdas. URLs externas (YouTube·TikTok·Drive·Vimeo) são tratadas no lado do servidor.
- Fila: todas as entradas × partes × idiomas compartilham um pool; uma fila cheia é reverificada a cada 5 minutos. Um erro de engine em uma parte cancela os outros idiomas dessa parte; uma parte silenciosa passa o original diretamente; um guarda de inatividade impede que fique pendurado para sempre.
- Salvamento: as partes são mescladas de volta em um arquivo por (entrada × idioma). Uma saída não dividida mantém o nome do arquivo Perso; uma mesclada fica
<nome-original>.dublado.<lang>.<ext>; colisões recebem_2,_3….
Lip-sync
Lip-sync (boca sincronizada com o áudio dublado) é executado após a dublagem, no projeto de dublagem finalizado. Apenas vídeo — entradas de áudio são rejeitadas. É um trabalho longo: execute em segundo plano e avise o usuário de antemão que leva consideravelmente mais tempo que a dublagem. Créditos (a cobrança do servidor é autoritativa): dublagem ≈ segundos ×1 · lip-sync ≈ ×2 · ambos ≈ ×3 — dublagem agora + lip-sync depois custa o mesmo que ambos de uma vez. Fontes 4K+: cada taxa ×3 nos planos pro/business/enterprise (ex.: um dub+lip-sync de 1 min em 4K ≈ 60×3×3 = 540) — mencione isso quando o vídeo for 4K.
Escolha o fluxo pelo que existe:
- Vídeo novo + lip-sync — um comando executa toda a cadeia (dub → lip-sync → save); avise que ambos os estágios cobram:
node scripts/dubbing.mjs "<arquivo|URL>" --target en --lipsyncSe linhas[credit-check]forem impressas e ele parar, a estimativa excede os créditos restantes: mostre essas linhas (URL de recarga incluída) e execute novamente com--forceapenas após o usuário recarregar ou aprovar continuar mesmo assim. - Dublado anteriormente nesta sessão — toda execução concluída imprime uma linha
[project-ref] {...}. Guarde-a; nunca a mostre ao usuário. Lip-sync sem re-dublagem (×2, sem cobrança de dublagem):node scripts/dubbing.mjs --lipsync-only '<esse [project-ref] JSON>' - Nenhum [project-ref] nesta sessão — pergunte se o usuário sabe o número do projeto no portal Perso (
--lipsync-only <número>, ×2). Caso contrário, o vídeo deve ser dublado novamente (--lipsync, ×3) — confirme antes de re-dublar.
Regras:
- Repetir lip-sync no mesmo projeto cobra novamente (sem dedup no servidor). Se esta sessão já fez lip-sync nele, aponte para o arquivo existente e execute novamente apenas com confirmação explícita.
- Se o lip-sync falhar, o worker salva o vídeo dublado em vez disso e informa no relatório final — transmita isso claramente; os créditos de dublagem não são desperdiçados.
- Créditos acabando entre dublagem e lip-sync: os vídeos dublados são salvos e continuar termina apenas o lip-sync — transmita a URL de recarga e continue pelo caminho
[resume-state]assim que pago (veja Interrupção e retomada).
Falante (apenas projetos de dublagem)
Para atribuir um novo falante a uma ou mais frases de um projeto já dublado, execute:
node scripts/speaker.mjs "<URL do projeto|projectSeq>" --at "5s,22s,98s" (códigos de tempo/intervalos separados por vírgula)
node scripts/speaker.mjs "<URL do projeto|projectSeq>" --text "primeira linha|segunda linha" (separado por pipe, texto entre aspas)
node scripts/speaker.mjs "<URL do projeto|projectSeq>" --sentence 1234,1288 (reexecutar após uma pergunta [sentence-select])
node scripts/speaker.mjs "<URL do projeto|projectSeq>" --list (navegar pelas frases do projeto para encontrar o alvo certo)
Apenas projetos de dublagem — o worker rejeita projetos de legenda (STT), separação de áudio e lip-sync, e projetos inacabados/falhos. Ele descobre o workspace sozinho a partir do link do projeto; você não precisa perguntar qual espaço.
Uma pausa não é uma falha. Uma linha [sentence-select] significa que o worker não conseguiu fixar um único alvo (nada foi dado, nada correspondeu, ou mais de uma frase correspondeu) — ele sai limpo para perguntar algo, não porque falhou. A lista numerada abaixo dela (índice, rótulo de tempo, texto) é segura para transmitir ao usuário como está. Logo após essa lista, uma linha [sentence-ref] imprime um mapa JSON dos mesmos números para os números de frase internos do worker — essa linha é apenas para sua consulta: nunca a mostre ou leia em voz alta. Assim que o usuário escolher um número, procure-o nesse mapa e execute novamente o mesmo comando com --sentence definido para esse valor. Um bloco [speaker-resolve] junto a ele significa que alguns alvos já foram resolvidos, mas nada foi escrito ainda — ele tem sua própria lista numerada e sua própria linha [sentence-ref]; a lista --sentence da reexecução deve incluir tanto esses valores já resolvidos quanto o(s) novo(s) escolhido(s), ou os resolvidos seriam descartados. Quando uma execução imprime mais de um grupo [sentence-select] (vários alvos ambíguos de uma vez), cada grupo
…
[truncado para tradução]