/dubbing
Una skill que dobla videos automáticamente a través de la API de Perso AI Dubbing.
Reglas principales (deben cumplirse)
- Solo el worker ve la clave raw. Nunca abras la clave con
Read, la hagas eco o la pases como argumento de línea de comandos. Los workers la envían solo en el encabezadoXP-API-KEY. - Un comando = todo el trabajo.
scripts/dubbing.mjsmaneja por sí mismo subir · dividir · traducir · fusionar · guardar. Nunca ejecutesprepare_input.mjsoprobe_split.mjsantes — son herramientas de depuración, yprobe_split.mjsrealiza una subida real (el video se subiría dos veces). - Ejecuta siempre
dubbing.mjsen segundo plano. Los trabajos toman de minutos a horas; un timeout del shell en primer plano mata la ejecución a medio camino. - Idiomas no especificados → origen
auto, destinoen. - Varios idiomas van en un solo comando (
--target en,zh,ja) — la subida/división ocurre una vez y se reutiliza por idioma. Nunca ejecutes una vez por idioma (vuelve a subir el origen cada vez). - Transmite el progreso fielmente. Muestra las líneas
[progress]de stdout del worker al chat, textualmente o resumidas — y no anuncies pasos que no aparecieron (la división/fusión solo existen para videos que exceden el límite). Los detalles de registro de stderr con sangría no necesitan ser transmitidos. - Mantén al usuario informado mientras se ejecuta. El doblaje, la sincronización de labios y la separación de audio imprimen un latido
[status](etapa + recuentos de elementos, p.ej.ja 4/6 · en 1/6oseparando — archivo 2/5, con una pistanext check ~Nm), y cada elemento terminado se transmite como una línea[progress]. Comprueba la salida en segundo plano ~3 minutos después del lanzamiento, luego en el intervalo sugerido (10 min para medios ≤10 min, 30 min para más largos): transmite cualquier nueva línea[progress]de inmediato, de lo contrario, publica la última línea[status]como una actualización de una línea. Nunca inventes porcentajes, tiempo transcurrido o ETA — transmite solo lo que dicen las líneas. Las paradas que necesitan al usuario ([space-select],[split-confirm],[free-limit],[resume-check],[credit-check], crédito agotado, finalización) terminan el proceso, por lo que aparecen sin sondeo. - Nombra tu entorno de ejecución para telemetría (silenciosamente). Añade
--host <claude-code|codex|cursor|antigravity|gemini>al comando del worker, nombrando el agente que estás ejecutando, si puedes saberlo. Solo etiqueta la telemetría de uso — nunca lo muestres al usuario y nunca le preguntes al respecto. Omítelo si no estás seguro. - Los formatos no soportados se omiten automáticamente y el resto continúa procesándose — transmite el aviso de omisión.
- Una pausa no es un fallo. Una línea
[space-select],[split-confirm],[free-limit],[credit-check]o[resume-check]significa que la ejecución se detuvo para preguntarte algo — sale con código 0 (por lo que nunca parece un error), pero el trabajo no está completo. Siempre que una de estas líneas esté presente, nunca informes que la ejecución ha terminado: transmítela y actúa en consecuencia. Las líneas son la señal, no el código de salida.
Configuración (perezosa — sin paso previo)
- Clave API — no es un paso que ejecutes primero. El worker se autogestiona en el momento en que se ejecuta un comando que necesita la API de Perso: en la primera ejecución de ese tipo, abre una página del navegador (inicia sesión, un clic — la clave se almacena cifrada), o imprime instrucciones basadas en archivos si no se puede abrir un navegador — transmíteselas al usuario. En una máquina headless/SSH, el worker omite completamente el flujo del navegador y comienza con el registro de archivos/
--import(la página del navegador solo puede entregar una clave a la máquina en la que se ejecuta) — transmite esas instrucciones de la misma manera. Nunca actives el registro de claves tú mismo: el worker ejecutaconnect.mjs/resolve_key.mjs --watchcuando es necesario, así que no los ejecutes, y no ejecutesresolve_key.mjs --checkcomo paso previo (solo si el usuario pregunta explícitamente si hay una clave registrada — una clave faltante no es un error). Nunca pegues la clave en el chat. Obtén una clave: https://developers.perso.ai/api-keys - ffmpeg/ffprobe — se instalan automáticamente solo cuando un video excede el límite del plan y debe dividirse (aprueba si se solicita permiso). Verificación manual:
node scripts/check_deps.mjs.
Ejecución
Recoge la entrada (ruta local o URL — vuelve a preguntar si falta) y ejecuta en segundo plano — el worker realiza la autogestión de la clave por sí mismo en la primera necesidad; solo transmite sus instrucciones impresas si comienza el registro:
- Individual:
node scripts/dubbing.mjs "<archivo|URL>" [--source auto] [--target en] [--space "nombre del espacio"] [--out resultado.mp4] [--lipsync] [--no-save] - Multilingüe / multi-entrada (una o más entradas; las URL y los archivos se pueden mezclar):
node scripts/dubbing.mjs "<URL>" "<archivo>" --target en,ja— una salida por entrada × idioma, guardada junto a cada origen (--out <carpeta>las recoge). - Carpeta (lote):
node scripts/dubbing.mjs "<carpeta>" [--target en,zh] [--recursive] [--out carpeta-de-salida]
Códigos de idioma — --target toma un código de idioma (en, ja) o una etiqueta regional. Las variantes regionales existen solo para Inglés (en = EE. UU. por defecto, en-GB = Reino Unido), Portugués (pt = Brasil por defecto, pt-PT = Portugal) y Español (es = México por defecto, es-ES = España); en-US, pt-BR y es-MX son ortografías aceptadas de esos valores por defecto. Cada otro idioma tiene una sola variante, y --source solo necesita el idioma (una etiqueta regional allí se trata como el idioma simple).
- Región nombrada en la solicitud → usa esa etiqueta exacta. Una región sin variante (p.ej., inglés australiano) → dile al usuario que solo existen las opciones listadas y deja que elija antes de ejecutar.
- Los tokens de dos letras son códigos de idioma, no países:
ukes Ucraniano — el inglés británico esen-GB. Misma trampa paraca(Catalán),be(Bielorruso),eu(Vasco). Si las palabras del usuario podrían significar cualquiera de los dos, pregunta cuál quiere decir antes de ejecutar. - Idioma nombrado sin región → la variante por defecto. Para esos tres idiomas, nombra la variante por defecto aplicada en la nota de entrega (p.ej., "Español (México) — usa
es-ESpara España").
Ubicación de guardado — --out toma ya sea una carpeta (un directorio existente, o una ruta que termina en /) que recoge los resultados dentro de ella, o una ruta de archivo para una sola entrada (la extensión real se añade si la ruta no tiene ninguna). Por defecto es junto a cada archivo fuente; no preguntes. En el primer trabajo de la sesión, menciónalo en la línea de inicio (p.ej. "Los resultados se guardarán junto a los originales — dímelo ahora si prefieres una carpeta diferente") y comienza de inmediato, sin esperar una respuesta. Si el usuario nombra una carpeta — con esta solicitud o en cualquier momento posterior en esta sesión — ejecuta con --out "<carpeta>" y sigue usando esa carpeta para cada trabajo posterior hasta que la cambie. Si redirige después de que ya haya comenzado una ejecución, mueve los archivos terminados allí cuando la ejecución se complete. Excepción: cuando cada entrada es una URL (sin carpeta de origen local), pregunta dónde guardar antes de comenzar.
Selección de espacio — con varios espacios de trabajo, el worker se detiene antes de subir, imprime líneas [space-select] (nombre | (plan) | créditos restantes) y se detiene. Muestra al usuario SOLO esas opciones (sin números internos), pregunta cuál, y vuelve a ejecutar con --space "<nombre del espacio>". Un espacio → sin pregunta; PERSO_SPACE_SEQ lo fija.
Confirmación de división — si la entrada excede el límite de duración o tamaño y debe dividirse & fusionarse automáticamente (doblaje, doblaje+sincronización de labios, o separación de audio), el worker se detiene e imprime líneas [split-confirm]. Transmítelas y pregunta al usuario: supera el límite de duración/tamaño, por lo que necesita división automática → procesamiento → fusión, que puede resultar menos pulido que dividirlo ellos mismos — ¿proceder automáticamente? En caso afirmativo, vuelve a ejecutar el mismo comando con --allow-split (nada se factura hasta este punto, por lo que volver a ejecutar es gratuito). Ejecuciones por lotes: --allow-split autoriza cada división en la ejecución.
No guardar (solo servidor) — cuando el usuario quiere el video doblado pero no guardado como archivo local, añade --no-save: el worker deja el resultado en el espacio de trabajo de Perso del usuario y omite la descarga, imprimiendo Mantenido en el servidor, no guardado: … → proyecto <seq> (la línea [project-ref] todavía se emite, por lo que el doblaje se puede sincronizar con labios o recuperar más tarde). Solo videos individuales/no divididos — el archivo fusionado de un video dividido necesita una descarga local, por lo que se guarda normalmente y el worker lo dice. No se puede combinar con --lipsync (el video con sincronización de labios debe descargarse).
Plan gratuito (los resultados permanecen en Perso) — un espacio de trabajo Gratuito puede doblar, sincronizar labios y separar normalmente, pero el servidor rechaza cada descarga, por lo que el worker nunca guarda un archivo allí. Imprime Plan gratuito — <nombre>: … listo en Perso → <url> (las descargas requieren un plan de pago) — transmite ese enlace como el resultado entregado: la ejecución está completa, nada falló y no hay nada que reanudar. Si el usuario quiere archivos reales, eso necesita un plan de pago — ofrece la ruta de actualización (ver Actualización de plan y créditos), nunca una re-ejecución.
Pausas de [free-limit] del plan gratuito — dos de ellas, ambas antes de que se facture algo:
- Vista previa de 30 segundos. Una entrada de doblaje/sincronización de labios más larga de 30 s se detiene con líneas
[free-limit]: en Gratuito solo se doblarían los primeros 30 segundos, el resto se omite. Transmite la pregunta, y en caso afirmativo vuelve a ejecutar el mismo comando con--allow-preview(el worker recorta los primeros 30 s localmente y envía solo eso). Un enlace de plataforma importado por Perso no se puede recortar — allí la respuesta es un archivo local, un clip más corto o una actualización. - Supera el límite del plan. Los medios que superan el límite de duración/tamaño del plan no reciben oferta de división en Gratuito (
--allow-splitno aplica): las líneas[free-limit]dicen que un archivo más corto o una actualización es la única opción. Transmítelas y detente.
Mientras se ejecuta (para explicar la espera):
- División: el archivo completo se sube primero; solo un rechazo por límite del plan instala ffmpeg y divide sin pérdida. Las URL externas (YouTube·TikTok·Drive·Vimeo) se manejan en el lado del servidor.
- Cola: todas las entradas × partes × idiomas comparten un solo grupo; una cola completa se vuelve a verificar cada 5 minutos. Un error del motor en una parte cancela los otros idiomas de esa parte; una parte silenciosa pasa el original; un guardia de inactividad evita que se cuelgue para siempre.
- Guardado: las partes se fusionan de nuevo en un archivo por (entrada × idioma). Una salida sin dividir mantiene el nombre de archivo de Perso; una fusionada es
<nombre-original>.doblado.<idioma>.<ext>; las colisiones obtienen_2,_3….
Sincronización de labios
La sincronización de labios (boca sincronizada con el audio doblado) se ejecuta después del doblaje, sobre el proyecto de doblaje terminado. Solo video — las entradas de audio son rechazadas. Es un trabajo largo: ejecuta en segundo plano e informa al usuario de antemano que toma considerablemente más tiempo que el doblaje. Créditos (la facturación del servidor es autorizada): doblaje ≈ segundos ×1 · sincronización de labios ≈ ×2 · ambos ≈ ×3 — doblaje ahora + sincronización de labios después cuesta lo mismo que ambos a la vez. Fuentes 4K+: cada tarifa ×3 en planes pro/negocios/empresa (p.ej., un doblaje+sincronización de labios de 1 min en 4K ≈ 60×3×3 = 540) — menciona esto cuando el video sea 4K.
Elige el flujo según lo que exista:
- Video nuevo + sincronización de labios — un comando ejecuta toda la cadena (doblaje → sincronización de labios → guardado); advierte que ambas etapas facturan:
node scripts/dubbing.mjs "<archivo|URL>" --target en --lipsyncSi se imprimen líneas[credit-check]y se detiene, la estimación supera los créditos restantes: muestra esas líneas (URL de recarga incluida), y vuelve a ejecutar con--forcesolo después de que el usuario recargue o apruebe continuar de todos modos. - Doblado anteriormente en esta sesión — cada ejecución terminada imprime una línea
[project-ref] {...}. Consérvala; nunca la muestres al usuario. Sincronización de labios sin re-doblaje (×2, sin cargo de doblaje):node scripts/dubbing.mjs --lipsync-only '<ese [project-ref] JSON>' - Sin [project-ref] en esta sesión — pregunta si el usuario sabe el número de proyecto del portal de Perso (
--lipsync-only <número>, ×2). De lo contrario, el video debe doblarse de nuevo (--lipsync, ×3) — confirma antes de re-doblar.
Reglas:
- Repetir la sincronización de labios en el mismo proyecto factura de nuevo (sin deduplicación del lado del servidor). Si esta sesión ya lo sincronizó, señala el archivo existente y vuelve a ejecutar solo con confirmación explícita.
- Si la sincronización de labios falla, el worker guarda el video doblado en su lugar y lo dice en el informe final — transmite eso claramente; los créditos de doblaje no se desperdician.
- Créditos agotados entre el doblaje y la sincronización de labios: los videos doblados se guardan y continuar solo termina la sincronización de labios — transmite la URL de recarga, y continúa a través de la ruta
[resume-state]una vez pagado (ver Interrupción y reanudación).
Hablante (solo proyectos de doblaje)
Para asignar un nuevo hablante a una o más oraciones de un proyecto ya doblado, ejecuta:
node scripts/speaker.mjs "<URL del proyecto|projectSeq>" --at "5s,22s,98s" (códigos de tiempo/ rangos separados por comas)
node scripts/speaker.mjs "<URL del proyecto|projectSeq>" --text "primera línea|segunda línea" (separados por tubería, texto entre comillas)
node scripts/speaker.mjs "<URL del proyecto|projectSeq>" --sentence 1234,1288 (volver a ejecutar después de una pregunta [sentence-select])
node scripts/speaker.mjs "<URL del proyecto|projectSeq>" --list (navegar por las oraciones del proyecto para encontrar el objetivo correcto)
Solo proyectos de doblaje — el worker rechaza proyectos de subtítulos (STT), separación de audio y sincronización de labios, y proyectos no terminados/fallidos. Descubre el espacio de trabajo por sí mismo a partir del enlace del proyecto; no necesitas preguntar qué espacio.
Una pausa no es un fallo. Una línea [sentence-select] significa que el worker no pudo identificar un solo objetivo (no se dio nada, no coincidió nada, o coincidió más de una oración) — sale limpiamente para preguntarte algo, no porque haya fallado. La lista numerada debajo (índice, etiqueta de tiempo, texto) es segura de transmitir al usuario tal cual. Justo después de esa lista, una línea [sentence-ref] imprime un mapa JSON de los mismos números a los números internos de oración del worker — esa línea es solo para tu consulta: nunca la muestres ni la leas en voz alta. Una vez que el usuario elija un número, búscalo en ese mapa y vuelve a ejecutar el mismo comando con --sentence configurado a ese valor. Un bloque [speaker-resolve] junto a él significa que algunos objetivos ya se resolvieron pero aún no se escribió nada — tiene su propia lista numerada y su propia línea [sentence-ref]; la lista --sentence de la re-ejecución debe incluir tanto esos valores ya resueltos como el/los recién elegido(s), o los resueltos se perderían. Cuando una ejecución imprime más de un grupo [sentence-select] (varios objetivos ambiguos a la vez), cada grupo
…
[truncado para traducción]