/dubbing
Uma skill que dubla automaticamente vídeos através da API Perso AI Dubbing.
Regras principais (devem ser seguidas)
- Apenas o worker vê a chave bruta. Nunca abra a chave com
Ler, a ecoe ou a passe como argumento de linha de comandos. Os workers enviam-na apenas no cabeçalhoXP-API-KEY. - Um comando = o trabalho completo.
scripts/dubbing.mjstrata do carregamento, divisão, tradução, junção e gravação por si só. Nunca executeprepare_input.mjsouprobe_split.mjsantes dele — são ferramentas de depuração e oprobe_split.mjsrealiza um carregamento real (o vídeo seria carregado duas vezes). - Execute sempre
dubbing.mjsem segundo plano. Os trabalhos demoram minutos a horas; um timeout da shell em primeiro plano mata a execução a meio. - Idiomas não especificados → fonte
auto, destinoen. - Vários idiomas num único comando (
--target en,zh,ja) — o carregamento/divisão acontece uma vez e é reutilizado por idioma. Nunca execute uma vez por idioma (recarrega a fonte de cada vez). - Transmita o progresso fielmente. Mostre as linhas
[progress]do stdout do worker no chat, na íntegra ou resumidas — e não anuncie passos que não apareceram (divisão/junção existem apenas para vídeos acima do limite). Os registos de detalhe indentados no stderr não precisam de ser transmitidos. - Mantenha o utilizador informado enquanto corre. Dublagem, lip-sync e separação de áudio imprimem todos um batimento cardíaco
[status](fase + contagens de itens, ex:ja 4/6 · en 1/6ouseparating — file 2/5, com uma dicanext check ~Nm), e cada item concluído é transmitido como uma linha[progress]. Verifique a saída em segundo plano cerca de 3 minutos após o lançamento e depois no intervalo sugerido (10 minutos para média ≤10 min, 30 min para mais longo): transmita qualquer nova linha[progress]imediatamente; caso contrário, publique a linha[status]mais recente como uma atualização de uma linha. Nunca invente percentagens, tempo decorrido ou ETAs — transmita apenas o que as linhas dizem. Paragens que necessitam do utilizador ([space-select],[split-confirm],[free-limit],[resume-check],[credit-check], créditos insuficientes, conclusão) terminam o processo, por isso são apresentadas sem necessidade de sondagem. - Nomeie o seu runtime para telemetria (silencioso). Adicione
--host <claude-code|codex|cursor|antigravity|gemini>ao comando do worker, nomeando o agente que está a executar, se conseguir identificá-lo. Serve apenas para marcar a telemetria de uso — nunca o mostre ao utilizador nem lhe pergunte sobre isso. Omita se não tiver a certeza. - Formatos não suportados são ignorados automaticamente e o resto continua a ser processado — transmita o aviso de omissão.
- 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 lhe perguntar algo — termina com código 0 (por isso nunca parece um erro), mas o trabalho não está concluído. Sempre que uma destas linhas estiver presente, nunca reporte 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 passo prévio)
- Chave de API — não é um passo que execute primeiro. O worker autogere-se no momento em que um comando que precise da API Perso é executado: na primeira execução desse tipo, abre uma página do navegador (iniciar sessão, um clique — a chave é armazenada encriptada) ou imprime instruções baseadas em ficheiro se nenhum navegador puder abrir — transmita-as ao utilizador. Numa máquina sem interface gráfica/SSH, o worker ignora totalmente o fluxo do navegador e começa com registo por ficheiro/
--import(a página do navegador só pode entregar uma chave à máquina onde é executada) — transmita essas instruções da mesma forma. Nunca acione o registo da chave você mesmo: o worker inicia oconnect.mjs/resolve_key.mjs --watchquando necessário, por isso não execute esses comandos, e não executeresolve_key.mjs --checkcomo passo prévio (apenas se o utilizador perguntar explicitamente se uma chave está registada — uma chave em falta 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 deve ser dividido (aprove se for solicitada permissão). Verificação manual:
node scripts/check_deps.mjs.
Executar
Recolha a entrada (caminho local ou URL — volte a perguntar se faltar) e execute em segundo plano — o próprio trabalhador realiza a verificação da chave na primeira necessidade; apenas transmita as suas instruções impressas se o registo for iniciado:
- Único:
node scripts/dubbing.mjs "<ficheiro|URL>" [--source auto] [--target en] [--space "nome do espaço"] [--out resultado.mp4] [--lipsync] [--no-save] - Multilíngue / multi-entrada (uma ou mais entradas; URLs e ficheiros podem ser misturados):
node scripts/dubbing.mjs "<URL>" "<ficheiro>" --target en,ja— um resultado por entrada × idioma, guardado junto de cada fonte (--out <pasta>recolhe-os). - Pasta (lote):
node scripts/dubbing.mjs "<pasta>" [--target en,zh] [--recursive] [--out pasta-de-resultados]
Códigos de idioma — --target aceita um código de idioma (en, ja) ou uma etiqueta regional. As variantes regionais existem apenas para Inglês (en = EUA padrão, en-GB = Reino Unido), 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 aceites desses padrões. Todos os outros idiomas têm uma única variante, e --source precisa apenas do idioma (uma etiqueta regional é tratada como o idioma simples).
- Região nomeada no pedido → use essa etiqueta exata. Uma região sem variante (ex: inglês australiano) → informe o utilizador de 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 de países:
uké Ucraniano — inglês britânico éen-GB. A mesma armadilha paraca(Catalão),be(Bielorrusso),eu(Basco). Se a formulação do utilizador puder significar um ou outro, pergunte qual quer dizer 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").
Localização de gravação — --out aceita uma pasta (um diretório existente ou um caminho que termine em /) que recolhe os resultados no seu interior, ou um caminho de ficheiro para uma única entrada (a extensão real é anexada se o caminho não tiver nenhuma). O padrão é junto de cada ficheiro de origem; não pergunte. No primeiro trabalho da sessão, indique-o na linha de arranque (ex: "Os resultados serão guardados junto dos originais — diga-me agora se preferir uma pasta diferente") e comece imediatamente, sem esperar por resposta. Se o utilizador nomear uma pasta — com este pedido ou a qualquer momento posterior nesta sessão — execute com --out "<pasta>" e continue a usar essa pasta para todos os trabalhos seguintes até que a altere. Se redirecionar após uma execução já iniciada, mova os ficheiros concluídos para lá quando a execução terminar. Exceção: quando todas as entradas são URLs (sem pasta de origem local), pergunte onde guardar antes de começar.
Seleção de espaço — com várias áreas de trabalho, o trabalhador para antes do carregamento, imprime linhas [space-select] (nome | (plano) | créditos restantes) e para. Mostre ao utilizador APENAS essas opções (sem números internos), pergunte qual e execute novamente com --space "<nome do espaço>". Um espaço → sem pergunta; PERSO_SPACE_SEQ fixa-o.
Confirmação de divisão — se a entrada exceder o limite de comprimento ou tamanho e tiver de ser automaticamente dividida e reunida (dublagem, dublagem+lip-sync, ou separação de áudio), o trabalhador para e imprime linhas [split-confirm]. Transmita-as e pergunte ao utilizador: excede o limite de comprimento/tamanho, por isso precisa de divisão automática → processamento → junção, que pode resultar menos polida do que dividir por si próprio — prosseguir automaticamente? Em caso afirmativo, execute novamente o mesmo comando com --allow-split (nada é faturado até este ponto, por isso reexecutar é gratuito). Execuções em lote: --allow-split autoriza todas as divisões na execução.
Não guardar (apenas servidor) — quando o utilizador quiser o vídeo dublado mas não guardado como ficheiro local, adicione --no-save: o trabalhador deixa o resultado na área de trabalho Perso do utilizador e salta a transferência, imprimindo Mantido no servidor, não guardado: … → projeto <seq> (a linha [project-ref] ainda é emitida, para que a dublagem possa ser sincronizada labialmente ou recuperada mais tarde). Apenas vídeos simples/não divididos — o ficheiro reunido de um vídeo dividido precisa de uma transferência local, por isso é guardado normalmente e o trabalhador informa isso. Não pode ser combinado com --lipsync (o vídeo com lip-sync tem de ser transferido).
Plano gratuito (resultados permanecem no Perso) — um espaço gratuito pode dublar, sincronizar lábios e separar normalmente, mas o servidor recusa todas as transferências, por isso o trabalhador nunca guarda um ficheiro lá. Imprime Plano gratuito — <nome>: … pronto no Perso → <url> (transferências precisam de um plano pago) — transmita esse link como o resultado entregue: a execução está concluída, nada falhou e não há nada para retomar. Se o utilizador quiser ficheiros reais, é necessário um plano pago — ofereça o caminho de atualização (veja Atualização de plano e créditos), nunca uma reexecução.
Pausas [free-limit] do plano gratuito — duas delas, ambas antes de qualquer faturação:
- Pré-visualização de 30 segundos. Uma entrada de dublagem/lip-sync com mais de 30 s para com linhas
[free-limit]: no plano gratuito, apenas os primeiros 30 segundos seriam dublados, o resto seria omitido. Transmita a pergunta e, em caso afirmativo, reexecutar o mesmo comando com--allow-preview(o trabalhador corta os primeiros 30 s localmente e submete apenas isso). Um link de plataforma importado pelo Perso não pode ser cortado — aí a resposta é um ficheiro local, um clipe mais curto ou uma atualização. - Acima do limite do plano. Média acima do limite de comprimento/tamanho do plano não recebe oferta de divisão no plano gratuito (
--allow-splitnão se aplica): as linhas[free-limit]dizem que um ficheiro mais curto ou uma atualização são a única saída. Transmita-as e pare.
Enquanto corre (para explicar a espera):
- Divisão: o ficheiro completo é carregado primeiro; apenas uma rejeição por limite do plano instala o ffmpeg e divide sem perdas. URLs externos (YouTube·TikTok·Drive·Vimeo) são tratados no lado do servidor.
- Fila: todas as entradas × partes × idiomas partilham um único conjunto; uma fila cheia é reavaliada a cada 5 minutos. Um erro de motor numa parte cancela os outros idiomas dessa parte; uma parte silenciosa passa o original; um guarda de inatividade evita que fique pendurado para sempre.
- Gravação: as partes são reunidas num ficheiro por (entrada × idioma). Uma saída não dividida mantém o nome do ficheiro Perso; uma reunida é
<nome-original>.dubbed.<lang>.<ext>; colisões recebem_2,_3….
Lip-sync
O lip-sync (boca sincronizada com o áudio dublado) é executado após a dublagem, no projeto de dublagem concluído. Apenas vídeo — entradas de áudio são rejeitadas. É um trabalho longo: execute em segundo plano e informe o utilizador antecipadamente que demora consideravelmente mais do que a dublagem. Créditos (a faturação do servidor é autoritária): 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/empresa/enterprise (ex: um dub+lip-sync de 1 min em 4K ≈ 60×3×3 = 540) — mencione isto quando o vídeo for 4K.
Escolha o fluxo pelo que existe:
- Novo vídeo + lip-sync — um comando executa toda a cadeia (dub → lip-sync → guardar); avise que ambas as fases faturam:
node scripts/dubbing.mjs "<ficheiro|URL>" --target en --lipsyncSe forem impressas linhas[credit-check]e parar, a estimativa excede os créditos restantes: mostre essas linhas (URL de recarga incluído) e reexecute com--forceapenas após o utilizador recarregar ou aprovar continuar de qualquer forma. - Dublado anteriormente nesta sessão — cada execução concluída imprime uma linha
[project-ref] {...}. Guarde-a; nunca a mostre ao utilizador. Lip-sync sem reedublagem (×2, sem custo de dublagem):node scripts/dubbing.mjs --lipsync-only '<esse JSON do [project-ref]>' - Sem [project-ref] nesta sessão — pergunte se o utilizador sabe o número do projeto a partir do portal Perso (
--lipsync-only <número>, ×2). Caso contrário, o vídeo tem de ser dublado novamente (--lipsync, ×3) — confirme antes de reedublar.
Regras:
- Repetir lip-sync no mesmo projeto fatura novamente (sem desduplicação no servidor). Se esta sessão já fez lip-sync nele, aponte para o ficheiro existente e reexecute apenas com confirmação explícita.
- Se o lip-sync falhar, o trabalhador guarda 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 a esgotar entre a dublagem e o lip-sync: os vídeos dublados são guardados e continuar conclui apenas o lip-sync — transmita o URL de recarga e continue pelo caminho
[resume-state]assim que for pago (veja Interrupção e retoma).
Orador (apenas projetos de dublagem)
Para atribuir um novo orador a uma ou mais frases de um projeto já dublado, execute:
node scripts/speaker.mjs "<URL do projeto|projectSeq>" --at "5s,22s,98s" (timecodes/intervalos separados por vírgulas)
node scripts/speaker.mjs "<URL do projeto|projectSeq>" --text "primeira linha|segunda linha" (separados 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 trabalhador rejeita projetos de legendagem (STT), separação de áudio e lip-sync, e projetos incompletos/falhados. Descobre a área de trabalho sozinho a partir do link do projeto; não precisa de perguntar qual o espaço.
Uma pausa não é uma falha. Uma linha [sentence-select] significa que o trabalhador não conseguiu identificar um único alvo (nada foi dado, nada correspondeu, ou mais de uma frase correspondeu) — termina corretamente para lhe perguntar algo, não porque falhou. A lista numerada abaixo dela (índice, etiqueta de tempo, texto) é segura para transmitir ao utilizador tal como está. Imediatamente após essa lista, uma linha [sentence-ref] imprime um mapa JSON dos mesmos números para os números de frase internos do trabalhador — essa linha é apenas para sua consulta: nunca a mostre nem a leia em voz alta. Assim que o utilizador escolher um número, procure-o nesse mapa e reexecute o mesmo comando com --sentence definido para esse valor. Um bloco [speaker-resolve] ao lado significa que alguns alvos já foram resolvidos mas nada foi ainda escrito — tem a sua própria lista numerada e a sua própria linha [sentence-ref]; a lista --sentence da reexecução deve incluir tanto esses valores já resolvidos como o(s) novo(s) escolhido(s), caso contrário os resolvidos seriam descartados. Quando uma execução imprime mais de um grupo [sentence-select] (vários alvos ambíguos em simultâneo), cada grupo tem a sua própria linha [sentence-ref]. Junte as chaves de todas elas numa única lista --sentence na reexecução.
… [truncado para tradução]