videolabs: crear un video (estilo curso en terminal)
Todo reside en un repositorio, /opt/opus/videolabs (github.com/bigzaqui/videolabs, público: nunca subir credenciales; ejecuta scripts/check-secrets.sh antes de hacer commit). El código de este skill está en skills/videolabs_create-video/ de ese repositorio, enlazado a ~/.claude/skills por scripts/setup.sh. Sus valores de configuración (claves de ElevenLabs y Cloudflare, el host de renderizado) están en el archivo .env ignorado por git en la raíz del repositorio; consulta .env.example.
El repositorio contiene: un motor genérico (herramientas Remotion + ElevenLabs, un npm ci) y una carpeta por laboratorio bajo labs/. Se ha utilizado para "kafka - in a nutshell" (26 lecciones / 18 min), "ceph - first steps" (14 lecciones / 11.6 min) y, anteriormente, "ceph in a nutshell" (41 lecciones / 30 min; su guión y elementos visuales se mantienen en reference/ como ejemplo de tarjetas, comparación, medidor, árbol, incidencia, estados y sitios). Ambos laboratorios actuales son prácticos: cada comando se ejecutó primero en un laboratorio real (ver "Laboratorio real").
/opt/opus/videolabs/
engine/ herramientas genéricas: engine/cli.mjs <lab> <comando>, src/ (Remotion), scripts/
labs/<slug>/
lab.config.json entrada del catálogo (título, descripción, etiquetas) + apariencia (marca, logotipo, barra de estado, nombres de herramientas, corte de muestra)
script/script.md narración, una escena por encabezado ###
visuals.ts qué muestra cada escena + anulaciones de señales
lab/ create-lab.sh, destroy-lab.sh, cap.sh, captures/, lab-meta.json
audio/raw/ salida de ElevenLabs (pagada; comprometida en git)
out/ data/ public/voiceover/ generado, ignorado por git
site/ scripts/publish-all.sh state/ el sitio y su publicación (skill videolabs_publish)
script.md ──cli escenas──▶ data/scenes.json
└─ cli tts (temporizaciones de palabras de ElevenLabs) ─▶ audio/raw ──pace──▶ public/voiceover/* + data/{durations,captions}.json
visuals.ts + engine/src/cues.ts (revelar = cuando se pronuncia la palabra) ─▶ Remotion ──cli render──▶ out/<slug>.mp4
cli player ─▶ out/player/{chapters.json,lab.json} ──videolabs_publish──▶ videolabs.pages.dev
Todos los comandos: node engine/cli.mjs <lab> <scenes|tts|pace|cues|stills|typecheck|studio|sample|render|player>, además de list y new <slug> (el README.md del repositorio tiene la tabla). La carpeta del laboratorio es lo único que difiere entre cursos; nunca copies código del motor en un laboratorio.
Flujo de trabajo (acordar el esquema y el guión con el usuario; todo lo demás se ejecuta sin preguntar)
Autorización permanente del usuario: una vez acordado el guión, ejecuta el tts pago de ElevenLabs, la auditoría de señales, el renderizado en el host de renderizado, el paso de reproductor/póster y la eliminación del laboratorio sin pedir permiso en cada paso. Informa los resultados y la nueva duración después. Solo detente para preguntar cuando algo esté fuera de este flujo de trabajo (por ejemplo, una acción destructiva sobre algo que el laboratorio no creó, o la publicación en el sitio cuando el usuario no lo ha solicitado).
-
¿Tema práctico? Primero construye y ejecuta un laboratorio real (ver "Laboratorio real" más abajo). Nunca escribas la salida de comandos de memoria: el primer borrador del guión de ceph-first-steps tenía errores en diez lugares una vez que se ejecutó en un clúster real. Luego escribe el guión a partir de lo que realmente sucedió.
-
Primero el guión, luego el video. Propón un esquema (módulos → lecciones, con el diagrama planeado por lección), obtén la aprobación, luego escribe
labs/<slug>/script/script.md. Cambiar texto es mucho más barato que regenerar audio. Marca a velocidad 1.0 con pausas de ≈ 160 palabras por minuto: 1.500 palabras ≈ 9.5 min incluyendo tarjetas de módulo; a 1.08 ≈ 168 ppm. Verifica la duración real con la líneaPaced … total X minjusto después dettsy ajusta antes de renderizar. Para comandos específicos de una versión, verifica el lanzamiento actual (documentación y las descargas/etiquetas de imagen del proyecto) primero. Formato (analizado porengine/scripts/build-scenes.mjs):## Módulo 1 — Arquitectura Central ### S02 · RADOS **Visual:** notas para el diseñador **Comandos:** `ceph -s` `ceph osd tree` (opcional, solo en pantalla) **Narración:** Un párrafo, escrito para ser hablado. Deletrea los comandos como se hablan ("ceph dash s").Termina la última lección de un módulo con "Conclusión del módulo: …" — la palabra conclusión activa la ventana de conclusión.
-
node engine/cli.mjs new <slug>crea la estructura delabs/<slug>/(configuración, un guión de tres escenas, un visuals.ts); completalab.config.json(título, descripción, etiquetas para el catálogo;brand,user,logo,statusbar,statusCmd,networkLabels,session,tools,samplepara la apariencia) y ejecutanode engine/cli.mjs <slug> scenes. -
Visuales en
labs/<slug>/visuals.ts(tipos enengine/src/types.ts,colorsde@engine/theme), una entrada por ID de escena. Tipos de diagrama (engine/src/components/Diagrams.tsx):topology: hosts (nombre, IP, chips de daemon, discos/réplicas) en una o dos líneas de red, una línea de estado opcional (etiquetastatusCmd, predeterminado de la configuración) yflows(bloques que se mueven entre hosts, desdecuehastauntil). Cualquier host/daemon/disco/estado puede tener uncue(aparece cuando se habla) ychanges: [{cue, state, label}](discoempty|osd|leader|idle|down|destroyed|new, hostok|spare|down|maint). Los elementos sin señal son visibles desde el principio. Construye el estado del laboratorio con ayudantes (host(n),built(n)) para que el diagrama crezca lección por lección.session: una terminal realista; cadacmdse escribe a sí mismo cuando se habla, luego sus líneasoutse imprimen; mantiene las filas más nuevas.hostestablece el prompt completo de una línea (predeterminadosession.promptde la configuración).topologycompacto opcional sobre él. La barra de comandos inferior está oculta para las escenas de sesión. Otros tipos:title(tarjeta fastfetch;user, filasextra),layers,cards,flow,tree,compare,gauge,terminal(salida de comandos con anotaciones),incident(síntoma/verificación/acción),checklist,states,sites;takeawayycommandCuesopcionales por escena. Usa salida real delab/captures/para cadaoutysymptom.
-
Revisa los visuales antes de pagar por el audio:
node engine/cli.mjs <slug> stills(un fotograma por escena al 60%;S04,S11 --at=12.5para un momento) y colócalos en mosaicos de 2×2 con ffmpegxstackpara examinarlos. Verifica desbordamiento, ajuste de línea, diseños vacíos. -
Voz: archivo
.enven la raíz del repositorio conELEVENLABS_API_KEY/ELEVENLABS_VOICE_ID, luegonode engine/cli.mjs <slug> tts(ejecútalo directamente, sin necesidad de aprobación; omite los existentes;--only=S03,S04 --forcepara rehacer solo las escenas cuya narración cambió, lo que también ahorra créditos). Ejecutapaceautomáticamente. Informa al usuario la nueva duración cuando el guión crezca. Haz commit delabs/<slug>/audio/(salida pagada). -
Auditoría de sincronización:
node engine/cli.mjs <slug> cues(después de cualquier cambio en un comando, cambia también su entradacommandCues: una etiqueta paradmesgque aún está señalada en "smartctl" es una discrepancia silenciosa) — lista la palabra que revela cada elemento y cada línea de comando (MISS= propagación entre vecinos). Corrige coincidencias incorrectas con anulaciones de señales (a continuación). Vuelve a ejecutar después de cualquier cambio de narración o voz. -
Vista previa:
node engine/cli.mjs <slug> sample(≈1 min en el servidor de renderizado), verifica fotogramas conffmpeg -ss <t> -frames:v 1,volumedetectpara niveles (sin recorte: máximo < -1 dB). -
Renderiza el curso completo (remoto, a continuación), luego
node engine/cli.mjs <slug> player, crea el pósterlabs/<slug>/out/spot.jpg, destruye las VMs del laboratorio y publica todo el sitio con el skill videolabs_publish.
Reglas de diseño aprobadas por el usuario
- Apariencia: escritorio Omarchy/Hyprland, paleta Tokyo Night (
src/theme.ts; nombres semánticos comorados,mon,cephfsson solo ranuras de paleta). Barra Waybar con módulos como espacios de trabajo 0–7, un estado (HEALTH_WARNen el módulo de solución de problemas), contador de lección07/41. Una ventana en mosaico, borde cuadrado de 2px, sin radio/brillo/gradiente. Cada lección se abre con un prompt robbyrussell escrito➜ module-1-core-architecture git:(s03) cat the-daemons.md, luego un# Heading. Las notas se renderizan como// comments, los comandos como líneas❯ cmd, las conclusiones como una ventana flotantecat sre-takeaway.md, los divisores de módulo comocd module-N-…+ título enorme, el abridor como fastfetch. Todo JetBrains Mono; Inter solo para subtítulos. Reemplazó una apariencia "genérica" de degradado oscuro que al usuario no le gustaba. - Sincronización de revelación: nunca escalonar por tiempo. Cada elemento aparece cuando el narrador dice su nombre por primera vez (
engine/src/cues.ts: el token significativo más largo, con prefijo, buscado hacia adelante desde la señal anterior). Anulaciones envisuals.tsdel laboratorio:cue: "=managers"palabra hablada exacta (se ignoran guiones/puntuación:"=write-ahead"),cue: null= sin palabra, interpolar.pointCues(lados de comparación),noteCue(árbol),linkCue(sitios),cues(insignias de título),commandCues(líneas de comando, por escena).- El orden de las etiquetas en
cueLabels()debe coincidir con el orden de índice que usa el renderizador enengine/src/components/Visuals.tsx(topología/sesión usan recorrido con clave entopologyKeys/sessionKeys). - Un cursor hacia adelante recorre todas las etiquetas de una escena, por lo que una etiqueta posterior que reutilice una palabra común ("tres", "recuperación", "añadir") puede aterrizar en una aparición posterior. Prefiere una palabra que aparezca una vez cerca del momento ("=repetir", "=comienza"), y siempre lee la auditoría de
cues.
- Subtítulos: frases simples, divididas al final de oración/cláusula, una línea ≤ 64 caracteres, sin resaltado de karaoke por palabra ("agotador").
- Respiración:
engine/scripts/pace.mjsinserta 250 ms después de. ? !y 120 ms después de: ;en el audio sin procesar de ElevenLabs y cambia las temporizaciones de palabras. Los puntos de corte provienen de ffmpegsilencedetect(estricto -38 dB/40 ms, luego -30 dB/20 ms), de lo contrario, la ventana más silenciosa de 5 ms, con fundidos de 8 ms — nunca solo de los límites de palabras de ElevenLabs (estos aterrizan dentro de las palabras). AjustaSENTENCE_PAUSE/CLAUSE_PAUSE, vuelve a ejecutarnode engine/cli.mjs <slug> pace(gratuito; el audio sin procesar se conserva enaudio/raw/). El ritmo depende de la versión de ffmpeg (7.1.5 y 8.0.1 dieron 17.9 y 17.8 min para el mismo audio de Kafka): ajusta el ritmo donde renderices, o copiadata/ypublic/voiceover/al host de renderizado. - Transiciones: fundidos cruzados de 15 fotogramas a través de
TransitionSeriesde@remotion/transitions; se superponen solo en la entrada silenciosa (0.4 s) / cola (0.8 s).engine/src/timeline.tsresta las superposiciones;engine/scripts/player.mjsystills.mjsreflejan esas constantes — mantenlas sincronizadas. - Voz elegida por el usuario: ElevenLabs "Mark"
v3p1kjzUvro6S76qmYmH(Biblioteca de Voces; funciona para TTS aunqueGET /v1/voices/{id}devuelve 404),eleven_multilingual_v2, velocidad 1.08, punto finalwith-timestamps,previous_text/next_textpara continuidad. Siempre verifica un ID de voz con una llamada TTS de 1 oración antes de una ejecución completa (un ID anterior en.envestaba truncado).
Configuraciones por curso (labs//lab.config.json)
Los espacios de trabajo en la Waybar y los recuentos de módulo/lección de fastfetch siguen las escenas automáticamente. Todo lo demás específico del curso son datos en la configuración, nunca código: brand (nombre de Waybar), user (prompt en la tarjeta de título), logo (cinco líneas de letras en bloque), statusbar (texto ok/warn, texto right, warnModule = el módulo que muestra la advertencia), statusCmd y networkLabels (valores predeterminados de las escenas de topología), session.prompt/session.title (sesiones de terminal), tools (palabras de comando que se omiten al señalar automáticamente una línea de comando), sample (IDs de escena del corte de Muestra), además de title, description, tags para el catálogo. Un nuevo laboratorio con una apariencia diferente no necesita cambios en el motor; un nuevo tipo de diagrama o estado sí (engine/src/types.ts, components, cues.ts) y luego beneficia a todos los laboratorios.
Prácticas de Remotion aplicadas (de los skills remotion-*)
Fuentes a través de @remotion/google-fonts (nunca confíes en las fuentes del sistema); <Audio> de @remotion/media con from; TransitionSeries; calculateMetadata dimensiona la composición a partir de las duraciones de audio; anima con interpolate/Easing.bezier y translate (sin transiciones CSS). No adoptado a propósito: Interactive.create/composiciones conectadas (solo útil para edición manual en Studio), mínimos de texto grande para video social, efectos de sonido de meme.
Renderizado
-
VIDEOLABS_RENDER_HOST(destino SSH del servidor de renderizado y laboratorio, claves ya configuradas) proviene del archivo.enven la raíz del repositorio: ejecutaset -a; . /opt/opus/videolabs/.env; set +aantes de los siguientes comandos, y escribe esa variable, no una dirección literal, en cualquier cosa que hagas commit. El mismo host ejecuta las VMs del laboratorio. -
TMPDIR=/var/tmpes establecido por el CLI:/tmpes un tmpfs de RAM pequeño en estos hosts y una mezcla de audio larga falla con "No space left on device" al final. -
Máquina local (4 vCPU) ≈ 45 min por 30 min de video. Servidor de renderizado remoto $VIDEOLABS_RENDER_HOST (Ubuntu 26.04, 24 hilos, 31 GB RAM, RTX 3090 Ti, KVM; Node 22, ffmpeg, rsync y las librerías de Chromium de apt) renderiza el curso de ceph de 11.6 min en 3 min y el curso de Kafka de 18 min en 5 min con concurrencia 12–16. El repositorio reside en
/opt/opus/videolabsen ambas máquinas. Flujo:rsync -az --delete --exclude node_modules --exclude out --exclude .env --exclude .git --exclude site --exclude state ./ $VIDEOLABS_RENDER_HOST:/opt/opus/videolabs/ # desde /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> player(Para mantener el tiempo idéntico al que revisaste, sincroniza
labs/<slug>/dataypublic/voiceovercon rsync en lugar de ejecutarscenes/paceen el servidor). Nunca copies.envallí. Ejecuta renderizados largos en segundo plano (nohup bash script &iniciado conssh -f -n) y espera con un Monitor en la línearealdetime; no busques en el registro "error" (la línea de comando tiene--log=error). Si el código de Waybar/diagrama cambia, verifica ambos laboratorios (stills), ya que lo comparten. -
Verificar un cambio en el motor: renderiza el corte de Muestra con el código antiguo y el nuevo y compara con
ffmpeg -i a.mp4 -i b.mp4 -lavfi ssim -f null -; los archivosscenes/durations/captions.jsonregenerados y la auditoría decuesdeben coincidir con la salida (el único parámetro que cambia la duración de la voz es si el texto del guión cambió, porquettsya se ejecutó). Cuando actualices la versión de ElevenLabs (voice id o modelo), vuelve a ejecutarttspara cada laboratorio +cues audit + pace + sampleantes de renderizar.
Laboratorio real (solo para temas prácticos)
Usa máquinas virtuales KVM en $VIDEOLABS_RENDER_HOST, creadas con los scripts de la carpeta labs/<slug>/lab/. create-lab.sh prepara las VMs (por ejemplo, para ceph: 1 admin + 3 mon + 3 osd, debian bookworm, ssh -F config). Ejecuta los comandos reales en el laboratorio real, captura la salida y las capturas de pantalla a captures/; luego intégralos en la sesión y otros visuales. El laboratorio se ejecuta en el mismo servidor que el renderizado; el CLI puede invocarlo, o puedes acceder por SSH manualmente y pegar las salidas en el guión. Los scripts de laboratorio son parte del repositorio y se comprometen como cualquier otro código. Teardown: labs/<slug>/lab/destroy-lab.sh, que se ejecuta después del renderizado o cuando ya no se necesita (nunca dejes VMs en funcionamiento).
Publicación
Hecho por el skill videolabs_publish para publicar el sitio completo (todos los cursos) en Cloudflare Pages. Se necesita autorización del usuario. El sitio incluye un reproductor web (build desde engine/player/) que divide el video en módulos/lecciones, tiene un modo oscuro/claro, reproduce con subtítulos y permite navegar por escena.
Enlaces y referencias
- Repositorio: [bigzaqui/videolabs] (https://github.com/bigzaqui/videolabs)
- Motor: Remotion + ElevenLabs
- Estilo: Omarchy / Tokyo Night terminal
- Sitio publicado: videolabs.pages.dev (cuando se publique)
Consejos adicionales
- Siempre verifica que el guión no tenga errores de hecho antes de ejecutar tts (paga). Una vez generado el audio, cambiar el texto requiere regenerar y pagar de nuevo.
- Para cursos largos, considera dividir el renderizado en partes o usar el servidor remoto para ahorrar tiempo.
- La sincronización de señales (cues) es crítica para la calidad del video; revísala siempre después de cualquier cambio.
- El laboratorio real es obligatorio para temas prácticos; no confíes en la memoria para las salidas de comandos.
- Si el usuario no especifica un tema, pregúntale antes de proceder.
- Si el usuario quiere editar un video existente, primero verifica si el guión y el audio ya existen; si es así, solo regenera el video con los cambios necesarios.
Solución de problemas
- Error de espacio en disco durante la mezcla de audio: asegúrate de que
TMPDIR=/var/tmpesté establecido. - Fallas en tts: verifica que la clave API de ElevenLabs sea válida y que el ID de voz exista. Prueba con una oración primero.
- Renderizado lento: usa el servidor remoto. Si el servidor remoto no está disponible, reduce la concurrencia.
- Problemas de sincronización: ejecuta la auditoría de señales (
cues) y corrige las anulaciones envisuals.ts. - Errores de compilación de TypeScript: ejecuta
node engine/cli.mjs <slug> typecheckpara verificar los tipos.
Laboratorios existentes
kafka-in-a-nutshell(26 lecciones, 18 min)ceph-first-steps(14 lecciones, 11.6 min)ceph-in-a-nutshell(referencia, 41 lecciones, 30 min)
Cada laboratorio tiene su propia configuración, guión y visuales; el motor es compartido.
Licencia
El código del motor y los laboratorios es de código abierto (repositorio público). El audio generado por ElevenLabs es propiedad del usuario (pagado).