Capas independientes, projectM en el mapping y catalogo de la galeria
Mapping - Los clips salian del reves: mapper.js activaba UNPACK_FLIP_Y_WEBGL, pero el modelo va con origen arriba-izquierda y texImage2D ya sube la primera fila en t=0, asi que el flip la mandaba al final. Comprobado renderizando el mapper real en Chromium headless con una fuente mitad roja / mitad azul. - projectM se puede usar YA en una capa: los .milk de data/presets-projectm se traducen a Butterchurn en el navegador al elegirlos (milkdrop-preset- converter). La capa MilkDrop gana 'biblioteca' (butter | projectm) sin cambiar su firma, para no recrear el contexto WebGL al cambiar de una a otra. Medido: 100/100 presets de una muestra convierten, 84/84 de los que llevan shaders warp/comp; ~7 ms por preset. - Cada capa tiene su pestana arriba y su propia vista, y '+ CAPA' crea una y entra en ella: con varias capas, ir y volver a MAPPING no era viable. - Dos capas MilkDrop sin preset ya no salen identicas (cogian el indice 0): cada instancia elige uno al azar y lo escribe en el estado. - Una capa nueva ya no nace en la fuente "motor", que es el lienzo del motor activo y hacia que dos capas ensenaran lo mismo. Galeria de visuales - scripts/catalogar-visuales.py: ficha de cada clip (pelicula, personajes, duracion) y, sobre todo, si es una silueta de verdad y cuanta figura tiene. Distingue silueta de corte crudo por el negro puro del fondo: 0,63-0,90 frente a 0,04-0,06, sin zona gris. - El panel lista los clips agrupados por pelicula, con nombre legible y aviso de los que casi no tienen figura, en vez del nombre del archivo. - Soporte de siluetas con canal alfa (.webm VP9): transparencia de verdad, sin recorte por luminancia. El shader del mapper ya la respeta. Documentacion - docs/visuales.md nuevo; pendiente.md al dia con lo hecho y lo que queda. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5d5223db9f
commit
f017246f0e
30 changed files with 3771 additions and 292 deletions
|
|
@ -20,16 +20,32 @@ para consultarse a saltos después.
|
|||
Cómo correr FOSFENO en un portátil Debian, Ubuntu o Mint, sin Raspberry Pi.
|
||||
|
||||
- [Uso del panel](uso.md)
|
||||
Cómo se maneja desde el móvil, qué hace cada motor de visuales y cómo se
|
||||
configura cada opción.
|
||||
Cómo se maneja desde el móvil, las dos pestañas (MOTORES y MAPPING), qué
|
||||
hace cada motor de visuales y cómo se configura cada opción.
|
||||
|
||||
- [El Mezclador VJ](mezclador.md)
|
||||
Las mezclas a fondo: las dos capas, cómo poner visuales de MilkDrop de
|
||||
fondo, los modos de mezcla y los efectos de color.
|
||||
|
||||
- [La galería de visuales](visuales.md)
|
||||
El catálogo de clips: cómo se generan las fichas, cómo distingue una silueta
|
||||
de un trozo de película y por qué unos clips salen marcados como flojos.
|
||||
|
||||
- [Projection mapping](mapping.md)
|
||||
Cómo deformar las visuales para encajarlas en superficies físicas: malla,
|
||||
máscaras y edición desde el panel arrastrando con el ratón o el dedo.
|
||||
Cómo deformar las visuales para encajarlas en superficies físicas, y cómo
|
||||
poner **un visual distinto en cada zona**: capas, fuentes, malla y máscaras.
|
||||
|
||||
- [Solución de problemas](problemas.md)
|
||||
Qué hacer cuando algo no arranca, no se ve o no suena. Incluye cómo leer
|
||||
los mensajes de error que aparecen en el propio panel.
|
||||
|
||||
- [Seguridad](seguridad.md)
|
||||
Qué asume el diseño, qué pasa si pinchas en una wifi pública y cómo cerrar
|
||||
el panel con una clave compartida (el QR ya la lleva dentro).
|
||||
|
||||
- [Arquitectura](arquitectura.md)
|
||||
Para quien vaya a tocar el código: las tres piezas, el estado, los eventos
|
||||
de WebSocket, cómo se añade un motor y los criterios que sigue el proyecto.
|
||||
|
||||
Si solo quieres empezar rápido, el archivo `README.md` de la raíz del
|
||||
proyecto tiene la versión resumida.
|
||||
|
|
|
|||
237
docs/arquitectura.md
Normal file
237
docs/arquitectura.md
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
# Arquitectura
|
||||
|
||||
Esta página es para quien vaya a **tocar el código** (o para uno mismo dentro de
|
||||
seis meses). Explica cómo está montado FOSFENO, dónde vive cada cosa y cómo se
|
||||
añade algo nuevo sin romper el resto.
|
||||
|
||||
## Las tres piezas
|
||||
|
||||
```
|
||||
┌───────────────┐ WebSocket ┌────────────────┐ WebSocket ┌──────────────┐
|
||||
│ PANEL │ ────────────▶ │ SERVIDOR │ ────────────▶ │ ESCENARIO │
|
||||
│ web/panel/ │ ◀──────────── │ backend/ │ ◀──────────── │ web/stage/ │
|
||||
│ (el móvil) │ estado │ server.py │ estado │ (Chromium) │
|
||||
└───────────────┘ └───────┬────────┘ └──────┬───────┘
|
||||
│ subprocess │ HDMI
|
||||
▼ ▼
|
||||
projectM (nativo) [ proyector ]
|
||||
```
|
||||
|
||||
- **Servidor** (`backend/server.py`, Flask + Flask-SocketIO). Guarda **el
|
||||
estado**, sirve los archivos web, lanza y vigila projectM, enruta el audio con
|
||||
`pactl` y persiste lo que hay que persistir.
|
||||
- **Panel** (`web/panel/`). La interfaz del móvil. **No calcula nada**: refleja
|
||||
el estado y manda órdenes.
|
||||
- **Escenario** (`web/stage/`). La página que sale por el proyector. Es la que
|
||||
**de verdad pinta**: Butterchurn, Hydra, Shaders y el Mezclador viven aquí,
|
||||
igual que el detector de BPM y la captura de audio.
|
||||
|
||||
Regla de oro: **el estado vive en el servidor**, y todo el mundo lo recibe
|
||||
entero en cada cambio. Ni el panel ni el escenario guardan verdades propias; si
|
||||
algo hay que recordar, va al estado.
|
||||
|
||||
## El estado
|
||||
|
||||
Es un único diccionario en `server.py` (`state`) que se difunde con
|
||||
`socketio.emit("state", state)` en cada cambio. Sus ramas:
|
||||
|
||||
| Rama | Qué guarda |
|
||||
|------|------------|
|
||||
| `engine` | motor activo: `projectm`, `butterchurn`, `hydra`, `shaders`, `mixer` |
|
||||
| `power` | visuales encendidas o en negro |
|
||||
| `sensitivity` | ganancia aplicada al audio antes del análisis |
|
||||
| `audio` | fuente (`mic` / `monitor`), tarjeta elegida y BPM detectado |
|
||||
| `butterchurn` | preset, cambio automático (segundos o compases) y transición |
|
||||
| `hydra` / `shaders` | el código que se está ejecutando y su etiqueta |
|
||||
| `mixer` | las dos capas (`source`, `fondo`, `camOn`, `video`), modo de mezcla y efectos |
|
||||
| `projectm` | categoría, visual, variante, favoritos, monitor, congelado |
|
||||
| `mapping` | `enabled`, `edit`, `masks[]` y `surfaces[]`, donde cada superficie es una **capa** (geometría + `fuente` + opacidad + mezcla) |
|
||||
| `meta` | listas detectadas: presets, cámaras, micrófonos, vídeos, monitores |
|
||||
| `status` / `notifications` / `network` | qué se ve, avisos e IP para el QR |
|
||||
|
||||
Lo que sobrevive a un reinicio se guarda en `data/`:
|
||||
`mapping.json` (mapping) y `pm-favoritos.json` (favoritos de projectM). El resto
|
||||
del estado es de la sesión.
|
||||
|
||||
## Eventos de WebSocket
|
||||
|
||||
**Del panel al servidor**
|
||||
|
||||
| Evento | Qué hace |
|
||||
|--------|----------|
|
||||
| `set_power`, `set_engine`, `set_sensitivity` | los tres mandos básicos |
|
||||
| `update_settings {engine, patch}` | parchea una rama del estado (`butterchurn`, `mixer`, `audio`, `projectm`) |
|
||||
| `run_code {engine, code, label}` | ejecuta código Hydra o GLSL |
|
||||
| `engine_command {action}` | `next`, `prev`, `lock`, `fix_current` |
|
||||
| `set_mapping {…}` | activar, editar, superficies y máscaras (persiste) |
|
||||
| `pm_favorito {action…}` | añadir, borrar o lanzar un favorito de projectM |
|
||||
| `rescan_devices`, `rescan_videos`, `reacquire_audio` | volver a mirar el hardware y la galería |
|
||||
| `system {action}` | apagar o reiniciar la máquina |
|
||||
|
||||
**Del escenario al servidor**
|
||||
|
||||
| Evento | Qué hace |
|
||||
|--------|----------|
|
||||
| `stage_meta` | publica lo que ha detectado el navegador (micrófonos, cámaras, presets) |
|
||||
| `stage_status {label, bpm}` | qué se está viendo y a cuántos BPM |
|
||||
| `stage_notify {level, message}` | manda un aviso a la banda del panel |
|
||||
| `audio_route {target}` | pide enrutar la captura al monitor del sistema o al micro |
|
||||
| `set_mapping` | al arrastrar los tiradores sobre las propias visuales |
|
||||
|
||||
**Del servidor a los clientes**: `state` (el estado entero), `status` (solo
|
||||
etiqueta y BPM, más ligero), `notify`, `stage_command`, `stage_rescan`,
|
||||
`stage_reacquire`.
|
||||
|
||||
**Nada de esto pide credenciales por defecto.** Cualquiera que alcance el
|
||||
puerto puede emitir cualquier evento, y eso incluye apagar la máquina y
|
||||
ejecutar código en el escenario. Si se configura `auth.token`, el handler de
|
||||
`connect` rechaza a quien no lo traiga en `auth`, y con eso queda cerrado todo
|
||||
lo demás de golpe. Ver [Seguridad](seguridad.md).
|
||||
|
||||
Lo que entra por el socket **no es de fiar**, así que se filtra antes de tocar
|
||||
el estado: `update_settings` pasa por una lista blanca de claves por motor
|
||||
(`CLAVES_AJUSTES`), y `set_mapping` sanea capas y máscaras y limita cuántas
|
||||
acepta. `mapping.json` se escribe **con un segundo de retardo**: arrastrar un
|
||||
punto emite ~16 veces por segundo y no queremos escribir la microSD a ese
|
||||
ritmo.
|
||||
|
||||
## Los motores
|
||||
|
||||
Cada motor es independiente: si a uno le falta su librería o revienta al
|
||||
arrancar, **los demás siguen funcionando**. `boot()` en `stage.js` los inicia
|
||||
uno a uno dentro de su propio `try`.
|
||||
|
||||
| Motor | Dónde corre | Lienzo |
|
||||
|-------|-------------|--------|
|
||||
| **projectM** | proceso nativo, ventana propia | ninguno (la página se queda en negro debajo) |
|
||||
| **Butterchurn** | navegador, WebGL | `#butterchurn` |
|
||||
| **Hydra** | navegador, WebGL | `#hydra` |
|
||||
| **Shaders** | navegador, WebGL a pelo | `#shaders` |
|
||||
| **Mezclador** | navegador, **sobre Hydra** | `#hydra` |
|
||||
|
||||
`applyState()` decide qué lienzo se ve y apaga los demás. El mezclador no es un
|
||||
motor aparte: **genera código Hydra** (`buildMixerCode`) a partir de los
|
||||
ajustes; por eso comparte lienzo con Hydra y nunca coinciden.
|
||||
|
||||
Butterchurn tiene una particularidad: puede estar pintando **sin ser el motor
|
||||
activo**, cuando hace de capa de fondo del mezclador. Esa condición está en un
|
||||
único sitio, `butterLive()`, y de ella dependen el preset, el cambio automático
|
||||
y los botones de siguiente/anterior.
|
||||
|
||||
### Añadir un motor nuevo
|
||||
|
||||
1. `ENGINES` en `server.py` y una rama en `state` con sus ajustes.
|
||||
2. Un lienzo en `web/stage/index.html` y su rama en `applyState()`.
|
||||
3. Un botón `.engine` en `web/panel/index.html` y su tarjeta de controles.
|
||||
4. Mostrar u ocultar esa tarjeta en `render()` de `panel.js`.
|
||||
5. Una entrada en `data/ayuda.json` para el botón de información.
|
||||
|
||||
## Audio y BPM
|
||||
|
||||
Toda la cadena de audio está **en el navegador** (`stage.js`):
|
||||
`getUserMedia` → `GainNode` (la sensibilidad) → `AnalyserNode`. De ahí beben el
|
||||
detector de ritmo, Butterchurn, Hydra y los shaders.
|
||||
|
||||
El detector (`BeatDetector`) mira la energía de graves, marca un pulso cuando
|
||||
supera su media reciente, agrupa los intervalos entre pulsos por parecido y se
|
||||
queda con el grupo mayoritario. El resultado se dobla o se divide hasta caer en
|
||||
70–180 BPM. Se publica en `fosBeat` y llega a los motores como `u_bpm`/`u_beat`
|
||||
(shaders) o `bpm` (Hydra).
|
||||
|
||||
La fuente **monitor** («audio del navegador») merece una nota: Chromium oculta
|
||||
los monitores de PulseAudio/PipeWire al enumerar dispositivos. El plan B es
|
||||
capturar la entrada por defecto y pedir al servidor que **mueva ese stream** al
|
||||
monitor de la salida con `pactl`. Solo funciona con servidor y escenario en la
|
||||
misma máquina — que es el caso del kiosko y del modo portátil.
|
||||
|
||||
## Mapping y capas
|
||||
|
||||
Dos módulos, con una división limpia: **`layers.js` produce imágenes** y
|
||||
**`mapper.js` las coloca**.
|
||||
|
||||
```
|
||||
layers.js mapper.js
|
||||
───────── ─────────
|
||||
capa 1 → Butterchurn → canvas ┐
|
||||
capa 2 → clip .mp4 → video ├→ resolver(surface) → textura → malla → #output
|
||||
capa 3 → ShaderEngine → canvas ┘ (WebGL)
|
||||
capa 4 → "motor" → el lienzo del motor activo
|
||||
```
|
||||
|
||||
**`layers.js`** mantiene una instancia por capa y la pone al día con el estado
|
||||
(`sync`): crea las nuevas, cambia el preset de las que lo hayan cambiado y
|
||||
destruye las borradas. Decide también qué se comparte y qué no:
|
||||
|
||||
- Con **estado propio** (Butterchurn, shaders): **una instancia por capa**, para
|
||||
que dos zonas puedan llevar presets distintos.
|
||||
- Sin estado (clips, cámara): **compartidas por contenido** con cuenta de usos,
|
||||
así el mismo clip en dos capas se reproduce una sola vez.
|
||||
|
||||
Cada capa se renderiza en un lienzo de **640×360**: el mapper la estira al
|
||||
deformarla, y esa resolución es lo que hace viable tener varias a la vez en una
|
||||
Raspberry.
|
||||
|
||||
**`mapper.js`** es el compositor WebGL. Para cada superficie pide su textura al
|
||||
`resolver` (que es `FosLayers.fuenteDe`), la sube **una sola vez por fotograma
|
||||
y por clave de fuente**, y la dibuja sobre la malla con una homografía por
|
||||
celda. Encima aplica opacidad (`uAlpha`) y modo de mezcla (`blendFunc`: normal
|
||||
o aditivo), y al final pinta las máscaras en negro.
|
||||
|
||||
Las superficies se guardan en coordenadas **normalizadas 0..1**, así que un
|
||||
mapping hecho a 1080p sigue valiendo en otra resolución. El formato completo
|
||||
está documentado en la cabecera de `mapper.js`. Guía de uso:
|
||||
[Projection mapping](mapping.md).
|
||||
|
||||
Una capa **no puede ser projectM**: es un proceso nativo con su propia ventana,
|
||||
fuera del alcance de un contexto WebGL del navegador. Su equivalente es la
|
||||
fuente `butter`, que son los mismos presets de MilkDrop.
|
||||
|
||||
## Estructura de archivos
|
||||
|
||||
```
|
||||
FOSFENO/
|
||||
├── backend/server.py Estado, WebSocket, procesos, audio, subidas
|
||||
├── web/panel/ Panel de control (móvil): index.html, panel.js, panel.css
|
||||
├── web/stage/ Escenario: stage.js (motores + audio), layers.js, mapper.js
|
||||
├── web/lib/ Librerías servidas tal cual (butterchurn, hydra, codemirror…)
|
||||
├── data/ Contenido y ajustes que persisten (ver abajo)
|
||||
├── scripts/ Arranque del kiosko, build de projectM, lib.sh
|
||||
├── docs/ Esta documentación
|
||||
├── install.sh Instalador multi-distro (Debian, Fedora, Arch, openSUSE)
|
||||
└── fosfeno Lanzador del modo portátil
|
||||
```
|
||||
|
||||
`data/` mezcla dos cosas: **contenido editable** que sí va al repositorio
|
||||
(`hydra-sketches.json`, `hydra-snippets.json`, `shaders.json`, `ayuda.json`,
|
||||
`presets-projectm/`) y **estado de cada instalación** que no va
|
||||
(`mapping.json`, `pm-favoritos.json`, `videos/`) — ver `.gitignore`.
|
||||
|
||||
## Criterios que sigue el código
|
||||
|
||||
Vale la pena respetarlos al tocar algo:
|
||||
|
||||
- **Nunca callar un fallo.** Todo error acaba en la banda de avisos del panel
|
||||
con una frase que dice qué hacer, no un volcado técnico. Para eso está
|
||||
`report()` en el escenario y `notify()` en el servidor.
|
||||
- **Que un fallo no se lleve el resto por delante.** Sin cámara, sin micro o sin
|
||||
projectM, lo demás sigue proyectando.
|
||||
- **El estado manda.** Si algo hay que recordar entre clientes, va al estado; no
|
||||
se guardan verdades locales en el panel.
|
||||
- **Textos en castellano y sin jerga.** Tanto la interfaz como los avisos están
|
||||
escritos para alguien que no ha leído el código.
|
||||
- **Nada de recursos huérfanos.** Cada instancia de motor lleva su `destruir()`
|
||||
y suelta lo suyo: `requestAnimationFrame`, temporizadores, `MediaStream` (el
|
||||
piloto de la cámara) y **el contexto WebGL** (`soltarGL`). El navegador solo
|
||||
aguanta ~16 contextos y al pasarse mata el más viejo, que es el del
|
||||
compositor de mapping: dejarlos colgando acaba en proyector negro.
|
||||
- **Cambiar un ajuste no recrea el motor.** En `layers.js`, la firma de una
|
||||
fuente decide si la instancia se reaprovecha; los ajustes (preset, cambio
|
||||
automático) se aplican en caliente con `aplicar()`.
|
||||
|
||||
### Trampa conocida: `hydra.hush()` vacía las fuentes
|
||||
|
||||
Al salir del Mezclador hacia otro motor se llama a `hydra.hush()`, y eso
|
||||
**borra `s0` y `s1`**. Por eso el escenario recuerda en `mixerS1` qué elemento
|
||||
tenía enganchado y lo vuelve a enlazar (`reengancharMixer`) al volver: sin
|
||||
eso, ir a Butter y volver dejaba el mezclador sin la capa de encima. Si algún
|
||||
día se añade otra fuente de Hydra, hay que recordarla igual.
|
||||
|
|
@ -6,7 +6,14 @@ Material gráfico que usa la documentación.
|
|||
- `raspberry-pi-5.jpg` — la placa Raspberry Pi 5. Se usa en `requisitos.md`.
|
||||
- `milkdrop.jpg` — ejemplo de visuales estilo MilkDrop. Se usa en `uso.md`.
|
||||
- `hydra.png` — el entorno de Hydra. Se usa en `uso.md`.
|
||||
- `panel.png` — captura del panel de control. Se usa en `uso.md` y el README.
|
||||
- `panel.png` — captura del panel de control (pestaña MOTORES). Se usa en
|
||||
`uso.md` y el README.
|
||||
- `mezclador.png` — la tarjeta del Mezclador VJ con visuales de fondo. Se usa
|
||||
en `mezclador.md`.
|
||||
- `mapping.png` — la pestaña MAPPING con la previsualización. Se usa en
|
||||
`mapping.md`.
|
||||
- `capas.png` — tres capas con visuales distintos (dos MilkDrop + un clip +
|
||||
una franja de shader). Se usa en `mapping.md` y el README.
|
||||
- `demo.mp4` — vídeo corto de FOSFENO en marcha. Se usa en el README
|
||||
(recomprimido para que no pese; el original eran 80 MB).
|
||||
|
||||
|
|
|
|||
BIN
docs/assets/capas.png
Normal file
BIN
docs/assets/capas.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 157 KiB |
BIN
docs/assets/mapping.png
Normal file
BIN
docs/assets/mapping.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 70 KiB |
BIN
docs/assets/mezclador.png
Normal file
BIN
docs/assets/mezclador.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 86 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 144 KiB After Width: | Height: | Size: 135 KiB |
|
|
@ -36,7 +36,7 @@ a la Raspberry el nombre de red `fosfeno`, y `fosfeno.local` es la forma
|
|||
estándar de localizar un equipo por su nombre en la red local.
|
||||
|
||||
**Escribir la dirección IP.** Es la que aparece en la pantalla de conexión del
|
||||
proyector, algo como `http://192.168.1.71/`. Funciona siempre, pero la IP puede
|
||||
proyector, algo como `http://192.168.1.XX/`. Funciona siempre, pero la IP puede
|
||||
cambiar de un día para otro, así que es la opción de reserva.
|
||||
|
||||
## El móvil tiene que estar en la misma red
|
||||
|
|
|
|||
203
docs/mapping.md
203
docs/mapping.md
|
|
@ -1,50 +1,164 @@
|
|||
# Projection mapping
|
||||
|
||||
> **Pestañas de capa.** Cada capa tiene su propia pestaña arriba, junto a
|
||||
> MOTORES y MAPPING, y el botón **+ CAPA** crea una y te deja ya dentro de
|
||||
> ella. Esa vista enseña solo la previsualización y los ajustes de *esa* capa:
|
||||
> es la forma de trabajar cuando hay varias, sin ir y volver a la lista.
|
||||
>
|
||||
> **Visuales de projectM en una capa.** La fuente "Visuales MilkDrop" tiene
|
||||
> ahora un selector de **biblioteca**: los ~100 presets de Butterchurn, o los
|
||||
> **9795 `.milk` de projectM** (categoría / visual / variante, o "uno al
|
||||
> azar"). Los `.milk` se traducen a Butterchurn en el propio navegador al
|
||||
> elegirlos. Ver `pendiente.md` punto 2 para el detalle y sus límites.
|
||||
|
||||
|
||||
FOSFENO puede **deformar las visuales** para encajarlas en superficies físicas
|
||||
(una pared en ángulo, cajas, un objeto, varias zonas de una fachada) en vez de
|
||||
proyectar un simple rectángulo. Todo se hace **desde el panel**, arrastrando con
|
||||
el ratón o el dedo, y se **guarda solo**.
|
||||
|
||||
Y no es una sola imagen troceada: **cada zona es una capa con su propio
|
||||
visual**. MilkDrop a la derecha, otro preset distinto a la izquierda y un vídeo
|
||||
en el centro, cada uno en su sitio y a la vez. Eso está en
|
||||
[Capas: un visual distinto en cada zona](#capas-un-visual-distinto-en-cada-zona).
|
||||
|
||||
Se aplica a los motores que corren en el navegador: **Butterchurn, Hydra,
|
||||
Shaders y Mezclador**. (projectM nativo aún no; ver [Limitaciones](#limitaciones).)
|
||||
|
||||
## Dónde está: la pestaña MAPPING
|
||||
|
||||
El mapping tiene **su propia pestaña**, a la derecha del título **FOSFENO**, al
|
||||
lado de **MOTORES**. Están separados a propósito: MOTORES es el directo
|
||||
(encendido, audio, motores) y MAPPING es el montaje. Así ni se estorban ni se
|
||||
confunden.
|
||||
|
||||
En la pestaña MAPPING la tarjeta ocupa el ancho entero, para que la
|
||||
previsualización sea grande y se puedan arrastrar los puntos con comodidad —
|
||||
también con el dedo, desde el móvil.
|
||||
|
||||
Mientras el mapping esté activado, la pestaña lleva un **punto verde**: desde
|
||||
MOTORES se ve de un vistazo que la salida se está deformando.
|
||||
|
||||
<div align="center">
|
||||
<img src="assets/mapping.png" alt="La pestaña MAPPING del panel" width="720">
|
||||
</div>
|
||||
|
||||
## En 4 pasos
|
||||
|
||||
1. Elige un motor que **no** sea projectM (Butterchurn, Hydra, Shaders o Mezcla).
|
||||
2. En el panel, sección **Mapping**, marca **Activar mapping**.
|
||||
3. Pulsa **+ Superficie**. Aparece un recuadro con las visuales en la
|
||||
previsualización del panel (y en el proyector).
|
||||
4. En la **previsualización** del panel, **arrastra los puntos** para encajar la
|
||||
forma sobre tu superficie real. Se ve en el proyector al instante.
|
||||
1. Entra en la pestaña **MAPPING** y marca **Activar mapping**.
|
||||
2. Pulsa **+ Capa**. Aparece un recuadro en la previsualización del panel (y en
|
||||
el proyector).
|
||||
3. **Arrastra los puntos** para encajar la forma sobre tu superficie real. Se ve
|
||||
en el proyector al instante.
|
||||
4. Con la capa seleccionada, en **Propiedades** eliges **qué se proyecta ahí**.
|
||||
|
||||
## La sección Mapping del panel
|
||||
## La pestaña MAPPING por dentro
|
||||
|
||||
Tiene tres bloques, de arriba abajo: la **salida**, las **capas** y las
|
||||
**propiedades** de la capa que tengas seleccionada.
|
||||
|
||||
**Salida**
|
||||
|
||||
- **Activar mapping** — enciende/apaga la deformación.
|
||||
- **Modo edición** — muestra u oculta los tiradores en la **ventana de las
|
||||
visuales** (por si prefieres ajustar ahí con un ratón). Para el uso normal no
|
||||
hace falta: con la previsualización del panel basta.
|
||||
- **+ Superficie** — añade una superficie nueva (un cuadrilátero).
|
||||
- **Editar en el escenario** — saca los tiradores y la malla de colores
|
||||
también sobre las propias visuales (por si prefieres ajustar ahí con un
|
||||
ratón). Para el uso normal **no hace falta**: con la previsualización del
|
||||
panel basta, y así el proyector sale limpio. Es un modo de montaje, no un
|
||||
ajuste: **arranca siempre apagado**, aunque lo dejaras puesto.
|
||||
- **Previsualización** — el recuadro donde arrastras los puntos (ratón o dedo).
|
||||
Cada capa lleva escrito su nombre y qué visual tiene dentro.
|
||||
|
||||
**Capas**
|
||||
|
||||
- **+ Capa** — añade una zona nueva. Sale escalonada, para que no tape a la
|
||||
anterior, y queda seleccionada.
|
||||
- **+ Máscara** — añade una **máscara**: un recuadro **negro** que tapa una zona
|
||||
(útil para recortar el derrame de luz fuera de la superficie física).
|
||||
- **Reset** — borra todas las superficies y máscaras.
|
||||
- **Previsualización** — el recuadro donde arrastras los puntos (ratón o dedo).
|
||||
Muestra una rejilla dentro de cada superficie para que veas cómo se deforma.
|
||||
- **Lista** — cada superficie y máscara con su tamaño de malla y un botón **✕**
|
||||
para borrarla.
|
||||
|
||||
> **Capa ≠ máscara.** Una **capa** proyecta algo (eliges qué en Propiedades);
|
||||
> una **máscara** solo tapa y no lleva visual. Es la confusión más fácil: si
|
||||
> creaste una máscara buscando dónde poner el visual, selecciónala y pulsa
|
||||
> **Convertirla en capa** — se sustituye por una capa en el mismo sitio y con
|
||||
> la misma forma.
|
||||
- **Reset** — borra todas las capas y máscaras.
|
||||
- La **lista** se lee como en cualquier mesa de VJ: **la de arriba es la que
|
||||
queda delante** en el proyector. Cada fila tiene:
|
||||
|
||||
| Botón | Qué hace |
|
||||
|-------|----------|
|
||||
| ◉ / ○ | Enciende o apaga la capa sin borrarla |
|
||||
| ▲ ▼ | La sube o la baja en el orden de pintado |
|
||||
| ✕ | La borra |
|
||||
|
||||
**Propiedades** — nombre, qué se proyecta, opacidad, mezcla y malla. Se explican
|
||||
abajo.
|
||||
|
||||
## Capas: un visual distinto en cada zona
|
||||
|
||||
Esto es lo que separa el mapping de FOSFENO de un simple recorte: **cada capa
|
||||
elige su propia fuente**, así que la proyección puede llevar cosas distintas a
|
||||
la vez.
|
||||
|
||||
| Fuente | Qué es | Coste |
|
||||
|--------|--------|-------|
|
||||
| **El motor de MOTORES** | Lo que esté puesto en la otra pestaña. En varias capas a la vez, todas enseñan lo mismo | ninguno |
|
||||
| **Visuales MilkDrop propias** | Un Butterchurn **solo para esa capa**, con su preset. Es lo que permite dos looks de MilkDrop distintos a la vez | alto |
|
||||
| **Shader GLSL** | Uno de los shaders de la librería, reaccionando al audio | medio |
|
||||
| **Clip o imagen** | Un archivo de `data/videos`. En bucle y sin sonido | bajo |
|
||||
| **Cámara** | La webcam | bajo |
|
||||
| **Negro** | Nada: tapa la zona sin usar máscara | ninguno |
|
||||
|
||||
Las capas de MilkDrop y de shader **son motores de verdad funcionando**: cada
|
||||
una se renderiza aparte y el mapper la estira al deformarla. Los clips y la
|
||||
cámara, en cambio, **se comparten**: si dos capas usan el mismo clip, se
|
||||
reproduce una sola vez.
|
||||
|
||||
### La receta del ejemplo
|
||||
|
||||
Tres zonas, tres visuales distintos:
|
||||
|
||||
1. **+ Capa** → nómbrala «Pared izquierda» → fuente **Visuales MilkDrop
|
||||
propias** → elige un preset.
|
||||
2. **+ Capa** → «Centro» → fuente **Clip o imagen** → elige tu vídeo.
|
||||
3. **+ Capa** → «Pared derecha» → fuente **Visuales MilkDrop propias** → *otro*
|
||||
preset, o marca **Cambiar de preset solo** para que vaya rotando.
|
||||
4. Arrastra cada una a su sitio en la previsualización.
|
||||
|
||||
<div align="center">
|
||||
<img src="assets/capas.png" alt="Tres capas con visuales distintos" width="820">
|
||||
</div>
|
||||
|
||||
### Transiciones
|
||||
|
||||
Las capas de **Visuales MilkDrop** llevan su propio deslizador de
|
||||
**transición (0–8 s)**: es lo que tarda un preset en disolverse en el
|
||||
siguiente. A 0 el cambio es un corte seco; subiéndolo, uno se funde con el
|
||||
otro sin que se note. Combinado con **Cambiar de preset solo**, la capa va
|
||||
alternando visuales sola y en suave.
|
||||
|
||||
El fundido lo hace el propio Butterchurn mientras carga, así que no cuesta
|
||||
rendimiento aparte.
|
||||
|
||||
### Opacidad y mezcla
|
||||
|
||||
Cuando dos capas se solapan, mandan estos dos ajustes:
|
||||
|
||||
- **Opacidad** — cuánto deja ver lo que hay debajo.
|
||||
- **Mezcla** — **Normal** tapa lo de debajo; **Sumar** apila luz, así que las
|
||||
zonas oscuras de la capa dejan pasar la de abajo. Sumar es lo que da los
|
||||
solapes bonitos entre visuales; normal es lo que quieres cuando cada capa va
|
||||
en su pared y no se tocan.
|
||||
|
||||
## Superficies con malla (curvar)
|
||||
|
||||
Una superficie nueva es un cuadrilátero de 4 esquinas (corrección de
|
||||
perspectiva / *keystone*). Para superficies **curvas**:
|
||||
Una capa nueva es un cuadrilátero de 4 esquinas (corrección de perspectiva /
|
||||
*keystone*). Para superficies **curvas**:
|
||||
|
||||
1. Selecciona la superficie (toca dentro de ella en la previsualización).
|
||||
2. Aparece **− malla / + malla**: cada **+ malla** la subdivide (2×2, 3×3…
|
||||
hasta 6×6).
|
||||
1. Selecciona la capa (toca dentro de ella en la previsualización).
|
||||
2. En Propiedades, **Malla**: cada **+** la subdivide (2×2, 3×3… hasta 6×6).
|
||||
3. Ahora puedes arrastrar **cualquier punto** de la rejilla (no solo las
|
||||
esquinas) para curvarla y adaptarla a superficies no planas.
|
||||
|
||||
Puedes crear **varias superficies** y mapear la misma imagen en distintas zonas.
|
||||
|
||||
## Máscaras
|
||||
|
||||
Una máscara es un cuadrilátero **negro** que se pinta encima de todo. Sirve para
|
||||
|
|
@ -60,18 +174,39 @@ cero, usa **Reset** o borra ese archivo.
|
|||
|
||||
## Editar en la ventana de las visuales (opcional)
|
||||
|
||||
Con **Modo edición** activo, los tiradores también aparecen sobre las propias
|
||||
visuales. Ahí, además, funcionan atajos de teclado:
|
||||
Con **Editar en el escenario** activo, los tiradores también aparecen sobre las
|
||||
propias visuales, con el nombre y la fuente de cada capa escritos encima. Ahí,
|
||||
además, funcionan atajos de teclado:
|
||||
|
||||
- **doble clic** en un hueco: crea una superficie nueva.
|
||||
- **+ / −**: subdivide/reduce la malla de la superficie seleccionada.
|
||||
- **Supr**: borra la superficie o máscara seleccionada.
|
||||
- **doble clic** en un hueco: crea una capa nueva.
|
||||
- **+ / −**: subdivide/reduce la malla de la capa seleccionada.
|
||||
- **Supr**: borra la capa o máscara seleccionada.
|
||||
|
||||
## Cuánto aguanta
|
||||
|
||||
Cada capa de **MilkDrop** o de **shader** es un motor de verdad renderizando
|
||||
aparte, así que tienen un coste real:
|
||||
|
||||
| Equipo | Capas de MilkDrop / shader | Capas de clip o cámara |
|
||||
|--------|---------------------------|------------------------|
|
||||
| Portátil con GPU | 3–4 sin despeinarse | muchas |
|
||||
| Raspberry Pi 5 | 2, con margen justo | varias |
|
||||
| Raspberry Pi 4 | 1 | unas pocas |
|
||||
|
||||
Cada capa se renderiza a **640×360** y el mapper la estira al deformarla: en una
|
||||
zona de la proyección no se nota, y es lo que hace viable tener varias a la vez.
|
||||
Si va lenta, lo primero que hay que quitar son las capas de MilkDrop de más;
|
||||
clips y cámara cuestan mucho menos, y además **se comparten** entre capas.
|
||||
|
||||
## Limitaciones
|
||||
|
||||
- **projectM nativo no se mapea todavía.** projectM corre en su propia ventana
|
||||
fuera del navegador, así que el mapper (que trabaja sobre las visuales web) no
|
||||
lo alcanza. Para mapear el mismo tipo de gráficos, usa **Butterchurn**
|
||||
(MilkDrop en el navegador). El soporte de projectM está en estudio.
|
||||
- El mapping es una capa de deformación sobre la imagen final; no cambia el
|
||||
contenido de cada motor.
|
||||
- **projectM nativo no puede ir en una capa.** projectM corre en su propia
|
||||
ventana fuera del navegador, así que el mapper no lo alcanza. Para el mismo
|
||||
tipo de visual, usa la fuente **Visuales MilkDrop propias**: son los mismos
|
||||
presets de MilkDrop, dentro del navegador, y además puedes tener varios
|
||||
distintos a la vez. Meter la ventana nativa como capa (capturándola) está en
|
||||
estudio.
|
||||
- El mapping deforma y compone; no cambia el contenido de cada motor.
|
||||
|
||||
Cómo está montado por dentro (el compositor WebGL, las capas, el formato de las
|
||||
superficies): [Arquitectura](arquitectura.md#mapping-y-capas).
|
||||
|
|
|
|||
182
docs/mezclador.md
Normal file
182
docs/mezclador.md
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
# El Mezclador VJ (mezclas)
|
||||
|
||||
El **Mezclador** es el motor de vídeo de FOSFENO. No es un motor de visuales
|
||||
generativas como los otros: es una **mesa de mezclas de dos capas** que combina
|
||||
en directo una capa de fondo con un clip, y les aplica efectos de color.
|
||||
|
||||
Se elige con el botón redondo **Mezcla** del apartado *Motor de visuales*, en la
|
||||
pestaña **MOTORES**.
|
||||
|
||||
## El modelo mental: siempre dos capas
|
||||
|
||||
Todo el mezclador se entiende con esta idea. Hay **dos capas y solo dos**, y
|
||||
siempre son las mismas:
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────┐
|
||||
│ CAPA DE ENCIMA :: el CLIP │ un vídeo o una imagen
|
||||
│ (data/videos/…) │ de la galería
|
||||
├──────────────────────────────────────────────┤
|
||||
│ CAPA DE ABAJO :: el FONDO │ la cámara web
|
||||
│ (cámara o visuales Butter / MilkDrop) │ o las visuales Butter
|
||||
└──────────────────────────────────────────────┘
|
||||
▼
|
||||
[ modo de mezcla ] → proyector
|
||||
```
|
||||
|
||||
La tarjeta del panel está numerada en ese mismo orden, y ese orden es la
|
||||
respuesta a casi cualquier duda:
|
||||
|
||||
| Paso | Qué decide |
|
||||
|------|------------|
|
||||
| **1 · Qué se ve** | si sale solo el fondo, solo el clip, o los dos |
|
||||
| **2 · Fondo** | qué hay en la capa de abajo: **cámara** o **visuales Butter** |
|
||||
| **3 · Clip** | qué vídeo o imagen va en la capa de encima |
|
||||
| **4 · Cómo se juntan** | el modo de mezcla (solo aparece si se ven los dos) |
|
||||
|
||||
Debajo del paso 4 hay una **línea naranja de resumen** que dice, en una frase,
|
||||
lo que va a salir por el proyector con los ajustes que tengas puestos. Si en
|
||||
algún momento no sabes qué estás montando, esa línea lo dice.
|
||||
|
||||
<div align="center">
|
||||
<img src="assets/mezclador.png" alt="La tarjeta del Mezclador VJ" width="480">
|
||||
</div>
|
||||
|
||||
## Poner visuales de fondo (MilkDrop detrás del vídeo)
|
||||
|
||||
Esto es lo que suele costar encontrar, así que va aparte. **Sí está integrado**
|
||||
y funciona así:
|
||||
|
||||
1. Motor **Mezcla**.
|
||||
2. Paso 2, desplegable **Fondo (capa de abajo)** → **Visuales Butter
|
||||
(MilkDrop)**.
|
||||
3. La cámara se apaga sola (piloto incluido) y su sitio en la capa de abajo lo
|
||||
ocupan las visuales de MilkDrop.
|
||||
4. Justo debajo aparece el **preset de MilkDrop** que hace de fondo, con
|
||||
**« Anterior / Siguiente »** y la casilla **Cambiar el fondo solo**.
|
||||
|
||||
Ese preset es **el mismo del motor Butter**: lo que elijas aquí queda elegido
|
||||
también allí, y al revés. El cambio automático también es el mismo (los
|
||||
segundos o los compases se ajustan en la tarjeta del motor Butter).
|
||||
|
||||
> **projectM no puede ir de fondo.** projectM es un programa nativo que corre
|
||||
> en su propia ventana, fuera del navegador, así que el mezclador no puede
|
||||
> leerlo como capa. El fondo de visuales es siempre Butterchurn — que es el
|
||||
> mismo MilkDrop, con los mismos presets, dentro del navegador.
|
||||
|
||||
## Receta: un personaje recortado sobre las visuales
|
||||
|
||||
Es el uso estrella del mezclador y encadena todo lo anterior:
|
||||
|
||||
1. **1 · Qué se ve** → **Los dos**.
|
||||
2. **2 · Fondo** → **Visuales Butter (MilkDrop)**, y elige ahí el preset.
|
||||
3. **3 · Clip** → un clip de silueta sobre negro (los `_silueta-negro` que
|
||||
genera DETEKTION, el proyecto hermano de `VISUALES/`, o cualquier vídeo con
|
||||
fondo negro).
|
||||
4. **4 · Cómo se juntan** → **Recorte (personaje delante)**.
|
||||
|
||||
El negro del clip se vuelve transparente y la figura queda **delante** de
|
||||
MilkDrop, latiendo con la música. En este modo el primer deslizador deja de ser
|
||||
la mezcla A/B y pasa a llamarse **Umbral del recorte**: súbelo si queda un halo
|
||||
oscuro alrededor de la figura, bájalo si se está comiendo partes del personaje.
|
||||
|
||||
## Paso 1 · Qué se ve
|
||||
|
||||
- **Solo el fondo** — a pantalla completa. El clip no se usa.
|
||||
- **Solo el clip** — a pantalla completa. El fondo no se usa (ni la cámara ni
|
||||
las visuales Butter: si eliges esto, el fondo queda atenuado en el panel).
|
||||
- **Los dos** — se combinan según el paso 4.
|
||||
|
||||
Los controles que en ese momento no pintan nada **no desaparecen: se atenúan**,
|
||||
para que se vea que existen sin que despisten.
|
||||
|
||||
## Paso 2 · Fondo (capa de abajo)
|
||||
|
||||
**Cámara.** Con la casilla *Cámara activada* y, si tienes más de una, el
|
||||
desplegable para elegirla. Si la cámara falla, el aviso sale en la banda de
|
||||
arriba del panel; para reintentar, apaga y enciende la casilla.
|
||||
|
||||
**Visuales Butter (MilkDrop).** Lo explicado más arriba. La cámara se libera de
|
||||
verdad mientras tanto.
|
||||
|
||||
## Paso 3 · Clip (capa de encima)
|
||||
|
||||
Los clips salen de la carpeta `data/videos/`. Hay dos formas de meterlos:
|
||||
|
||||
- **Copiándolos a la carpeta** y pulsando **Actualizar lista**.
|
||||
- **Con el botón Subir vídeo o imagen**, desde el móvil o el ordenador, sin
|
||||
tocar carpetas.
|
||||
|
||||
Formatos que van bien:
|
||||
|
||||
| Tipo | Formatos | Nota |
|
||||
|------|----------|------|
|
||||
| Vídeo | `.mp4` (H.264), `.webm` | En Raspberry, 720p o menos |
|
||||
| Imagen | `.jpg`, `.png`, `.gif`, `.webp` | Se queda fija como capa |
|
||||
|
||||
Los vídeos se reproducen **en bucle y sin sonido** (FOSFENO escucha la música de
|
||||
la sala, no la del clip).
|
||||
|
||||
## Paso 4 · Cómo se juntan
|
||||
|
||||
Solo aparece con **Los dos**.
|
||||
|
||||
| Modo | Qué hace |
|
||||
|------|----------|
|
||||
| **Recorte** | El negro del clip se vuelve transparente: la figura queda **delante** del fondo. El deslizador pasa a ser el umbral |
|
||||
| **Fundido** | Disuelve una capa sobre la otra según el deslizador de mezcla |
|
||||
| **Diferencia** | Resta las dos capas: contornos y colores invertidos donde coinciden |
|
||||
| **Multiplicar** | Oscurece: solo sobrevive lo que es claro en las dos |
|
||||
| **Sumar** | Aclara: suma la luz de las dos capas |
|
||||
| **Capa** | Superpone el clip usando su canal alfa |
|
||||
|
||||
## Los efectos de color
|
||||
|
||||
Se aplican **al resultado ya mezclado**, en este orden (importa: el
|
||||
caleidoscopio deforma antes de que el color entre en juego):
|
||||
|
||||
```
|
||||
caleidoscopio → rotación → pixelado → tono → saturación → contraste →
|
||||
brillo → colorama → posterizar → invertir → pulso al ritmo → feedback
|
||||
```
|
||||
|
||||
- **Mezcla A/B** — cuánta capa de encima frente a la de abajo (o el umbral, en
|
||||
modo Recorte).
|
||||
- **Tono / Saturación / Contraste / Brillo** — corrección de color de toda la
|
||||
vida.
|
||||
- **Colorama** — recicla los colores; en valores altos psicodelia pura.
|
||||
- **Posterizar** — reduce el número de colores por franjas.
|
||||
- **Pixelado** — baja la resolución aparente en bloques.
|
||||
- **Caleidoscopio** — repite la imagen en N sectores en espejo.
|
||||
- **Rotación** — gira la imagen.
|
||||
- **Feedback** — realimenta el fotograma anterior: estelas y túneles. Con
|
||||
valores altos la imagen tarda en limpiarse.
|
||||
- **Invertir colores** — el negativo.
|
||||
- **Pulso al ritmo** — la imagen late con los graves detectados (usa el mismo
|
||||
análisis de audio que alimenta el BPM).
|
||||
|
||||
## Por debajo
|
||||
|
||||
El mezclador **no es un motor aparte**: genera código [Hydra](https://hydra.ojack.xyz/)
|
||||
a partir de los controles y lo ejecuta. La cámara entra como fuente `s0` (o el
|
||||
lienzo de Butterchurn, si el fondo son visuales) y el clip como `s1`. Por eso
|
||||
el mezclador y el motor Hydra comparten el mismo lienzo y nunca están activos a
|
||||
la vez.
|
||||
|
||||
Como todo lo que corre en el navegador, la salida del mezclador **se puede
|
||||
mapear**: ver [Projection mapping](mapping.md).
|
||||
|
||||
## Si algo no se ve
|
||||
|
||||
FOSFENO no se queda callado: el motivo sale en la banda de avisos, arriba del
|
||||
panel.
|
||||
|
||||
| Aviso | Qué hacer |
|
||||
|-------|-----------|
|
||||
| «La cámara está apagada» | Marca *Cámara activada* en el paso 2 |
|
||||
| «No hay vídeo elegido» | Elige uno en el paso 3 |
|
||||
| «No hay vídeos» | Sube uno, o copia archivos a `data/videos` y pulsa *Actualizar lista* |
|
||||
| «No se pudo activar la cámara» | Comprueba el cable, pulsa *Buscar dispositivos de nuevo* y apaga/enciende la casilla |
|
||||
| «No se pudo cargar …» | El formato no lo traga el navegador: pásalo a `.mp4` (H.264) o `.webm` |
|
||||
|
||||
Más casos en [Solución de problemas](problemas.md).
|
||||
162
docs/pendiente.md
Normal file
162
docs/pendiente.md
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
# Pendiente
|
||||
|
||||
Lo que sabemos que falta o falla, con el diagnóstico ya hecho para no volver a
|
||||
investigarlo desde cero. Ordenado por lo que más duele.
|
||||
|
||||
---
|
||||
|
||||
## 1. Las capas no son de verdad independientes
|
||||
|
||||
**Lo que pasa.** Se pueden poner dos capas con visuales MilkDrop, pero acaban
|
||||
siendo **la misma imagen**. Y no hay forma de decir "en esta zona el Mezclador,
|
||||
en esta otra Butter", configurando cada una por su cuenta.
|
||||
|
||||
Son **tres cosas distintas** mezcladas en el mismo síntoma:
|
||||
|
||||
### 1a. Dos capas Butter sin preset elegido salen idénticas — ✅ HECHO
|
||||
|
||||
`web/stage/layers.js`, en `crearButter()`, hacía:
|
||||
|
||||
```js
|
||||
let i = Math.max(0, nombres.indexOf(f.preset));
|
||||
```
|
||||
|
||||
Con `preset: ""` (lo que quedaba si la lista de presets aún no había llegado
|
||||
al panel al crear la capa), `indexOf("")` devuelve `-1` y el `Math.max(0, -1)`
|
||||
lo dejaba en **0**: todas las capas así arrancaban en el mismo preset.
|
||||
|
||||
**Cómo quedó:** sin preset elegido, cada instancia coge uno **al azar** y lo
|
||||
devuelve en `presetElegido`. `sync()` avisa por `deps.onPreset(id, preset)`,
|
||||
que `stage.js` (`anotarPresetDeCapa`) escribe en el estado con `set_mapping`,
|
||||
así que el panel enseña cuál le tocó en vez de dejar el desplegable en blanco.
|
||||
No recrea la instancia: la firma de una capa butter sigue siendo el tipo a
|
||||
secas.
|
||||
|
||||
### 1b. Las capas puestas en "El motor de MOTORES" comparten imagen — ✅ HECHO
|
||||
|
||||
Esa fuente es, literalmente, el lienzo del motor activo. Dos capas así van a
|
||||
enseñar lo mismo siempre — no es un fallo, es lo que significa. El problema era
|
||||
que **era la fuente por defecto** de toda capa nueva, así que el primer
|
||||
resultado que veía cualquiera era "dos capas iguales".
|
||||
|
||||
**Cómo quedó:** una capa nueva ya no nace en "motor". `panel.js` tiene
|
||||
`fuenteDeCapaNueva()`, que la estrena en MilkDrop con preset al azar
|
||||
(contenido distinto desde el primer clic) y solo cae en "motor" si Butterchurn
|
||||
todavía no ha dado su lista de presets. Lo usan tanto `+ Capa` como
|
||||
`Máscara → Capa`.
|
||||
|
||||
### 1c. Falta el Mezclador (y Hydra) como fuente de capa — *(el trabajo de verdad)*
|
||||
|
||||
Hoy las fuentes son: motor, butter, shader, clip, cámara, negro. **No está el
|
||||
Mezclador**, así que "una zona con el Mezclador y otra con Butter" solo se
|
||||
puede hacer vía "motor", y entonces manda el selector global.
|
||||
|
||||
Por qué no está: el Mezclador **es** Hydra (genera código Hydra y lo ejecuta), y
|
||||
Hydra está montado como instancia única global (`makeGlobal: true`, con `s0`,
|
||||
`s1`, `o0` en el espacio global). Para tener dos mezcladores independientes hay
|
||||
que instanciar Hydra varias veces con `makeGlobal: false` y usar la API por
|
||||
instancia (`h.synth.src(...)`), lo que obliga a reescribir `buildMixerCode` para
|
||||
que no dependa de los nombres globales.
|
||||
|
||||
**Coste realista:** medio día, más el gasto de GPU de un Hydra por capa. Antes
|
||||
de meterse, decidir si compensa frente a la alternativa barata: dejar el
|
||||
Mezclador solo como motor a pantalla completa y que las capas tiren de
|
||||
MilkDrop/shader/clip, que es lo que ya funciona.
|
||||
|
||||
### 1d. Los dos paneles no están relacionados
|
||||
|
||||
MOTORES y MAPPING van cada uno por su lado: el preset que eliges en el motor
|
||||
Butter y el de una capa MilkDrop son listas distintas, no comparten favoritos,
|
||||
y lo que tocas en uno no se refleja en el otro.
|
||||
|
||||
**Hacia dónde:** una sola biblioteca de presets y una sola lista de favoritos,
|
||||
usada por el motor Butter, por las capas del mapping y por projectM. Esto se
|
||||
solapa con el punto 2: es el mismo trabajo de unificación.
|
||||
|
||||
---
|
||||
|
||||
## 2. projectM en el mapper — ✅ HECHO (camino A)
|
||||
|
||||
**Lo que pasaba.** Al elegir projectM no había forma de mapearlo ni de usarlo
|
||||
en una capa, y ahí está **la biblioteca de verdad**: 9795 presets `.milk` en 11
|
||||
categorías y 189 visuales. Butterchurn solo trae ~100 propios.
|
||||
|
||||
**Por qué no se podía.** projectM es un proceso nativo que pinta en su propia
|
||||
ventana X11/Wayland. El compositor de mapping es WebGL dentro del navegador y
|
||||
solo puede usar como textura lo que vive en la página.
|
||||
|
||||
**Cómo quedó.** Se descartó capturar la ventana (frágil en Wayland) y compilar
|
||||
projectM a WebAssembly (un proyecto en sí). Se hizo el **camino A**: traducir
|
||||
los `.milk` a presets de Butterchurn **en el propio navegador**, al elegirlos.
|
||||
|
||||
- `web/lib/milkdrop-preset-converter.min.js` (npm `milkdrop-preset-converter`,
|
||||
añadido a `web/package.json` y a `install.sh`).
|
||||
- `layers.js` → `cargarMilk(ruta)`: pide el `.milk` al backend (ya los servía),
|
||||
lo convierte y se lo pasa a `viz.loadPreset()`. Cachea **la promesa**, no el
|
||||
resultado, para que dos capas pidiendo el mismo preset lo conviertan una vez.
|
||||
- La capa MilkDrop tiene ahora `biblioteca: "butter" | "projectm"`. La firma de
|
||||
la capa **sigue siendo `'butter'`**, así que cambiar de biblioteca no recrea
|
||||
la instancia ni gasta un contexto WebGL más.
|
||||
- El panel tiene selector de biblioteca y, con projectM, los tres niveles
|
||||
(categoría / visual / variante) más un botón de "uno al azar".
|
||||
|
||||
**Medido, no supuesto:**
|
||||
|
||||
- **100/100** presets de una muestra repartida por toda la biblioteca se
|
||||
convierten sin excepción, incluidos **84/84** de los que llevan shaders
|
||||
`warp_1`/`comp_1` (que eran justo los dudosos).
|
||||
- Conversión de un `.milk` real en el navegador: **7 ms**.
|
||||
- Una capa del mapper con `Dancer/Glowsticks/285.milk` renderiza sin un solo
|
||||
aviso del escenario.
|
||||
|
||||
> Cuidado al medir esto: si el `.milk` no se descarga, Butterchurn **sigue
|
||||
> pintando su preset de reserva**. Una prueba que solo mire "¿hay píxeles
|
||||
> encendidos?" da OK igual. Por eso la comprobación exige además que el
|
||||
> escenario no haya soltado ningún aviso. La primera versión de la prueba dio
|
||||
> un falso OK exactamente por esto.
|
||||
|
||||
**Lo que queda de este punto:** la conversión no es perfecta al 100 % en
|
||||
fidelidad — algún shader complejo puede verse distinto del original en
|
||||
projectM. Se convierte y se ve, pero si un preset no convence, la salida es
|
||||
probar otra variante. Y falta unificar los favoritos entre el motor projectM
|
||||
y las capas (parte del punto 1d).
|
||||
|
||||
---
|
||||
|
||||
## 3. Los clips salían del revés en el mapping — ✅ ARREGLADO
|
||||
|
||||
**Lo que pasaba.** Al poner un vídeo en una capa del mapping, salía volteado
|
||||
verticalmente (boca abajo).
|
||||
|
||||
**Por qué.** `web/stage/mapper.js`, en `initGL()`, hacía
|
||||
`gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true)`. Pero el modelo del mapper va
|
||||
con el origen **arriba-izquierda** (`vUV.y = 0` es el borde de arriba de la
|
||||
superficie) y `texImage2D` ya sube la primera fila de la imagen en `t = 0`.
|
||||
Con el flip puesto, `t = 0` pasaba a ser la ÚLTIMA fila: todo del revés.
|
||||
Se notaba con los clips porque una visual de MilkDrop es casi simétrica y no
|
||||
canta.
|
||||
|
||||
**Comprobado**, no deducido: con una fuente mitad roja arriba / mitad azul
|
||||
abajo, renderizando el `mapper.js` real en Chromium headless y leyendo el
|
||||
píxel de arriba con `readPixels`, antes salía AZUL y ahora sale ROJO.
|
||||
|
||||
De paso se midió el **Mezclador** (Hydra, `s1.init({src})`) con la misma
|
||||
prueba: ese sale bien, no hay que tocarlo. Si alguna vez se ve del revés ahí,
|
||||
no es este fallo.
|
||||
|
||||
> Al medir esto, ojo con `readPixels` sobre un lienzo sin
|
||||
> `preserveDrawingBuffer`: devuelve negro y es facilísimo leerlo como "está
|
||||
> volteado". Hay que reservar el contexto con `preserveDrawingBuffer: true`
|
||||
> ANTES de que la librería llame a `getContext`.
|
||||
|
||||
---
|
||||
|
||||
## 4. Menor: no hay fundido al cambiar de motor
|
||||
|
||||
Pasar de Mezcla a Butter es un corte seco. Dentro de una capa MilkDrop sí hay
|
||||
transición suave (deslizador de 0–8 s), pero cruzar **dos motores** exige
|
||||
renderizar los dos a la vez durante el cruce y fundirlos en el compositor.
|
||||
|
||||
Se puede hacer en el mapper (dibujar la capa dos veces, con la fuente vieja
|
||||
bajando de alfa y la nueva subiendo), pero cuesta tener los dos motores vivos a
|
||||
la vez unos segundos. Decidir si merece la pena.
|
||||
94
docs/seguridad.md
Normal file
94
docs/seguridad.md
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
# Seguridad
|
||||
|
||||
FOSFENO es un **aparato de directo**, no un servicio en internet. Está pensado
|
||||
para una red local: tú, tu móvil y el proyector. Esta página cuenta qué asume
|
||||
el diseño, qué pasa si la red no es de fiar, y cómo cerrarlo.
|
||||
|
||||
## El modelo de amenaza, en una frase
|
||||
|
||||
Cualquiera que alcance `http://<ip-de-fosfeno>/` **manda sobre las visuales**.
|
||||
Por defecto no hay contraseña: es lo cómodo para casa, un ensayo o una red
|
||||
tuya, y es como funcionó desde el principio.
|
||||
|
||||
Eso está bien en tu salón. En **la wifi de un bar o de un festival** significa
|
||||
que un desconocido puede:
|
||||
|
||||
- Apagar o reiniciar el equipo en mitad de la sesión.
|
||||
- Cambiar de motor, de preset y de mapping.
|
||||
- **Ejecutar código** en el escenario, porque el editor de Hydra/GLSL es
|
||||
precisamente eso, y el navegador del kiosko arranca con permiso automático de
|
||||
**cámara y micrófono**.
|
||||
- Llenarte la tarjeta subiendo archivos a la galería.
|
||||
|
||||
No es un fallo escondido: es la consecuencia de no pedir credenciales. La
|
||||
solución está abajo y son dos líneas de configuración.
|
||||
|
||||
## La clave compartida
|
||||
|
||||
En `config.json`:
|
||||
|
||||
```json
|
||||
"auth": {
|
||||
"token": "loquesea-largo-y-tuyo"
|
||||
}
|
||||
```
|
||||
|
||||
O sin tocar el archivo, por variable de entorno:
|
||||
|
||||
```bash
|
||||
FOSFENO_TOKEN=loquesea-largo-y-tuyo ./fosfeno
|
||||
```
|
||||
|
||||
Con la clave puesta:
|
||||
|
||||
- **El código QR del proyector ya la lleva dentro.** Escaneas y entras, igual
|
||||
que siempre. No hay que teclear nada.
|
||||
- Quien solo conozca la IP **no se conecta**: el WebSocket rechaza la conexión,
|
||||
así que no puede ni mirar el estado ni mandar órdenes.
|
||||
- Las subidas a la galería también la piden.
|
||||
- El escenario la recibe del propio servidor al abrirse. No hay nada que
|
||||
configurar en el navegador.
|
||||
|
||||
La contrapartida: si escribes la dirección a mano tendrás que añadir
|
||||
`?k=tu-clave` al final. Por eso el QR es el camino cómodo.
|
||||
|
||||
> Con `token` vacío (el valor de fábrica) todo funciona exactamente como antes.
|
||||
> Actívala cuando pinches fuera de casa.
|
||||
|
||||
## Lo que ya está cerrado
|
||||
|
||||
Estas no dependen de que actives nada:
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **Path traversal** | Las rutas de archivos (`/panel`, `/stage`, `/lib`, `/data`, `/pm/variantes`) resuelven y comprueban que no salen de su carpeta. Probado con `../`, `%2e%2e%2f`, doble codificación y rutas absolutas |
|
||||
| **Nombres de archivo subidos** | Pasan por `secure_filename` y una lista blanca de extensiones. No se puede escribir fuera de `data/videos` ni subir un `.sh` |
|
||||
| **Inyección de comandos** | Ningún `shell=True`, ningún `os.system`. Todos los procesos se lanzan con lista de argumentos |
|
||||
| **Rutas de presets de projectM** | La categoría y el visual vienen del panel, así que se validan contra la carpeta de presets antes de usarse |
|
||||
| **XSS en el panel** | Los nombres de clips, capas y favoritos se pintan con `textContent`, nunca con `innerHTML` |
|
||||
| **CORS** | El WebSocket solo acepta su **mismo origen**. Antes admitía cualquiera, así que una web abierta en el móvil de alguien de la sala podía mandar órdenes |
|
||||
| **Tamaño de subida** | 512 MB por archivo (antes 4 GB, suficiente para llenar una microSD en tres peticiones) |
|
||||
| **Payloads del WebSocket** | Los ajustes se filtran por lista blanca de claves y las capas del mapping se sanean antes de guardarse, así que no se puede meter basura en el estado ni en `data/mapping.json` |
|
||||
|
||||
## Lo que NO deberías hacer
|
||||
|
||||
- **No lo expongas a internet.** Nada de abrir el puerto 80 en el router ni
|
||||
ponerle un túnel. No está pensado para eso y la clave compartida no es
|
||||
autenticación seria.
|
||||
- **No dejes el apagado remoto a mano de una red pública** sin clave. El
|
||||
instalador da permiso `sudo` sin contraseña para `reboot` y `poweroff`
|
||||
(`install.sh`), que es lo que hace que el botón del panel funcione.
|
||||
- **No pongas datos personales en `data/videos`.** Cualquiera que entre al
|
||||
panel puede listarlos y reproducirlos.
|
||||
|
||||
## Lo que no se sube al repositorio
|
||||
|
||||
`.gitignore` deja fuera lo de cada instalación: `.venv/`, `*.log` (el log del
|
||||
servidor lleva IPs), `data/mapping.json`, `data/pm-favoritos.json`,
|
||||
`data/videos/*` y `data/presets-projectm/`. En el repo solo van código,
|
||||
documentación y los presets de fábrica.
|
||||
|
||||
## Si encuentras algo
|
||||
|
||||
Es un proyecto pequeño de un hacklab: abre una incidencia en el Gitea del
|
||||
proyecto contando qué has visto y cómo reproducirlo.
|
||||
50
docs/uso.md
50
docs/uso.md
|
|
@ -18,6 +18,25 @@ conectado con la Raspberry. Rojo quiere decir que se ha perdido la conexión.
|
|||
|
||||

|
||||
|
||||
## Las dos pestañas: MOTORES y MAPPING
|
||||
|
||||
A la derecha del título **FOSFENO** hay dos pestañas. Separan las dos cosas que
|
||||
se hacen con el panel, que no tienen nada que ver entre sí:
|
||||
|
||||
- **MOTORES** — el directo. Encendido, audio, sensibilidad, el motor de
|
||||
visuales y sus controles. Es la pestaña de siempre y la que está abierta al
|
||||
arrancar.
|
||||
- **MAPPING** — el montaje. Encajar la imagen sobre la superficie física:
|
||||
superficies, malla y máscaras, con una previsualización grande para arrastrar
|
||||
los puntos.
|
||||
|
||||
Lo normal es pasar por MAPPING una vez al montar y quedarse en MOTORES el resto
|
||||
de la noche. Si el mapping está activado, aparece un **punto verde** en su
|
||||
pestaña, para que se sepa que la salida se está deformando aunque estés mirando
|
||||
MOTORES.
|
||||
|
||||
El panel recuerda en qué pestaña lo dejaste.
|
||||
|
||||
## Avisos y errores
|
||||
|
||||
Justo debajo de la cabecera aparecen los avisos. Si algo va mal (la cámara no
|
||||
|
|
@ -56,9 +75,10 @@ librería de fragmentos listos para usar; eliges uno y se carga en el editor.
|
|||
de código. Los shaders reciben información del audio y del ritmo, así que se
|
||||
mueven con la música.
|
||||
|
||||
**Mezclador.** El modo de vídeo. Mezcla la imagen de una webcam USB con clips
|
||||
de vídeo y efectos de color. Es lo más parecido a un programa de VJ como
|
||||
Resolume, pero funcionando dentro de la Raspberry.
|
||||
**Mezclador.** El modo de vídeo. Monta dos capas —un fondo (la webcam o las
|
||||
propias visuales de MilkDrop) y un clip encima— y las combina con efectos de
|
||||
color. Es lo más parecido a un programa de VJ como Resolume, pero funcionando
|
||||
dentro de la Raspberry.
|
||||
|
||||
## Audio y BPM
|
||||
|
||||
|
|
@ -92,15 +112,23 @@ código es GLSL y tienes los uniforms `u_time`, `u_bass`, `u_mid`, `u_treble`,
|
|||
|
||||
## El modo Mezclador
|
||||
|
||||
Primero copia tus clips de vídeo en la carpeta `data/videos` del proyecto.
|
||||
Aparecen solos en el desplegable de vídeo del panel. Para que vayan finos en
|
||||
la Raspberry conviene que sean clips cortos, en 720p o menos y en H.264.
|
||||
El mezclador monta **dos capas**: un **fondo** (la cámara web, o las visuales
|
||||
Butter/MilkDrop) y un **clip** encima (un vídeo o una imagen de la galería). La
|
||||
tarjeta del panel va numerada en ese orden: qué se ve, el fondo, el clip y cómo
|
||||
se juntan. Debajo hay una línea de resumen que dice, en una frase, lo que va a
|
||||
salir por el proyector.
|
||||
|
||||
En el panel eliges la fuente: solo la cámara, solo el vídeo, o la mezcla de
|
||||
las dos. Debajo tienes el modo de mezcla y una fila de controles de color:
|
||||
tono, saturación, contraste, brillo, colorama, posterizado, pixelado,
|
||||
caleidoscopio, rotación y feedback. La casilla de pulso al ritmo hace que la
|
||||
imagen lata con los graves.
|
||||
Para **poner visuales de fondo**, en el paso 2 cambia el desplegable de *Cámara*
|
||||
a **Visuales Butter (MilkDrop)**: la cámara se apaga y su sitio lo ocupan las
|
||||
visuales, con su propio selector de preset ahí mismo. Combinado con un clip de
|
||||
silueta y el modo *Recorte*, el personaje queda delante de MilkDrop.
|
||||
|
||||
Los clips se copian a `data/videos` o se suben desde el propio panel con el
|
||||
botón *Subir vídeo o imagen*. Para que vayan finos en la Raspberry conviene que
|
||||
sean cortos, en 720p o menos y en H.264.
|
||||
|
||||
La guía completa, con los modos de mezcla, los efectos de color y las recetas:
|
||||
[El Mezclador VJ](mezclador.md).
|
||||
|
||||
## Apagar y reiniciar
|
||||
|
||||
|
|
|
|||
160
docs/visuales.md
Normal file
160
docs/visuales.md
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
# La galería de visuales
|
||||
|
||||
Los clips que se ven en el Mezclador y en las capas del mapping viven en
|
||||
`data/videos`. Además del archivo suelto, hay un **catálogo**
|
||||
(`data/visuales.json`) con la ficha de cada uno: de qué película sale, quién
|
||||
aparece, cuánto dura y — lo importante — **si es una silueta de verdad y cómo
|
||||
de llena está**.
|
||||
|
||||
Sin esa ficha el panel solo puede enseñar `p3-davy-jones-tormenta_silueta-negro.mp4`.
|
||||
Con ella enseña *Davy Jones (en la tormenta)*, agrupado bajo *Piratas del
|
||||
Caribe: En el fin del mundo*, y marca los que casi no tienen figura.
|
||||
|
||||
---
|
||||
|
||||
## Cómo se genera
|
||||
|
||||
```bash
|
||||
cd ~/COFRE/CODERS/MUSIKA/VISUALES/FOSFENO
|
||||
|
||||
python3 scripts/catalogar-visuales.py # cataloga y escribe el JSON
|
||||
python3 scripts/catalogar-visuales.py --limpiar # + aparta los que no son siluetas
|
||||
```
|
||||
|
||||
Necesita `ffmpeg`/`ffprobe` y `numpy`. Tarda unos segundos por clip: decodifica
|
||||
2 fotogramas por segundo a 160x90 en gris y saca las cuentas de ahí.
|
||||
|
||||
Hay que volver a pasarlo **cada vez que entran clips nuevos** en la galería. El
|
||||
panel lo recarga solo al pulsar el botón de recargar del Mezclador, o al subir
|
||||
un archivo; si el JSON no está, el panel sigue funcionando con los nombres de
|
||||
archivo pelados.
|
||||
|
||||
## Qué mide, y por qué así
|
||||
|
||||
**¿Es una silueta o es un trozo de película tal cual?** En un clip siluetado
|
||||
por DETEKTION el fondo es negro **puro** (valor 0), cosa que no pasa nunca en
|
||||
una imagen de cine, por oscura que sea. Así que basta con medir qué porcentaje
|
||||
del cuadro está a cero:
|
||||
|
||||
| | negro puro |
|
||||
|---|---|
|
||||
| Clips siluetados de la galería | 0,63 – 0,90 |
|
||||
| Cortes sin siluetear | 0,04 – 0,06 |
|
||||
|
||||
No hay zona gris, y por eso el umbral está en 0,30. No es un número inventado:
|
||||
sale de medir la galería entera.
|
||||
|
||||
**¿Tiene figura suficiente?** Un clip puede estar perfectamente siluetado y aun
|
||||
así no servir, porque la escena era muy oscura y el recorte salió casi vacío.
|
||||
Se mide el porcentaje del cuadro por encima de gris 32, y se mira la **mediana**
|
||||
(no un fotograma suelto: casi todos los clips tienen tramos vacíos y tramos
|
||||
llenos, y juzgar por el fotograma central engaña).
|
||||
|
||||
| calidad | criterio | qué significa |
|
||||
|---|---|---|
|
||||
| `buena` | mediana ≥ 0,03 | se ve bien sobre el fondo |
|
||||
| `floja` | mediana < 0,03 | figura pequeña o muy oscura |
|
||||
| `vacia` | mediana < 0,005 o más de la mitad de fotogramas vacíos | apenas hay nada |
|
||||
| `descartable` | negro puro < 0,30 | no es una silueta: se ve el fondo |
|
||||
|
||||
## Qué hace `--limpiar`
|
||||
|
||||
Los `descartable` (los que enseñan el fondo entero de la película) se **mueven**
|
||||
a `data/videos/_descartados/`. No se borran: si un día quieres volver a pasarlos
|
||||
por DETEKTION, siguen ahí. El servidor no los sirve, porque solo lista archivos
|
||||
sueltos de `data/videos`, no subcarpetas.
|
||||
|
||||
Los `floja` y `vacia` **no se tocan**: son siluetas correctas, solo que oscuras.
|
||||
Se quedan en la galería marcadas, y ya decides tú si las usas — sobre un fondo
|
||||
de MilkDrop brillante, una silueta tenue puede funcionar.
|
||||
|
||||
## La ficha
|
||||
|
||||
```json
|
||||
{
|
||||
"archivo": "p3-davy-jones-tormenta_silueta-negro.mp4",
|
||||
"obra": "Piratas del Caribe: En el fin del mundo",
|
||||
"anio": 2007,
|
||||
"titulo": "Davy Jones (en la tormenta)",
|
||||
"tipo": "silueta",
|
||||
"modo": "silueta sobre negro",
|
||||
"origen": "detektion",
|
||||
"personajes": ["Davy Jones"],
|
||||
"etiquetas": ["en la tormenta"],
|
||||
"ancho": 1152, "alto": 480, "fps": 23.98, "duracion": 30.03,
|
||||
"metricas": { "negroPuro": 0.737, "figura": 0.0582,
|
||||
"figuraPico": 0.1974, "brillo": 8.49, "vacios": 0.03 },
|
||||
"clase": "silueta", "calidad": "buena", "aviso": "",
|
||||
"miniatura": "miniaturas/p3-davy-jones-tormenta_silueta-negro.jpg"
|
||||
}
|
||||
```
|
||||
|
||||
La miniatura no es un fotograma cualquiera: es **el fotograma con más figura**
|
||||
de todo el clip, que es el que dice de verdad qué vas a ver.
|
||||
|
||||
### De dónde salen los nombres
|
||||
|
||||
El catálogo lee las dos convenciones de DETEKTION:
|
||||
|
||||
- `siluetear.py` → `<algo>_silueta-<modo>.mp4` → `tipo: silueta`
|
||||
- `recortar.py` → `<peli>_13m26s.mp4` → `tipo: corte` (crudo, **sin** siluetear)
|
||||
|
||||
y del resto del nombre saca la película (`p1`…`p5`, `hackers`, `tron`…), los
|
||||
personajes conocidos y las palabras de escena. Las tablas están arriba del
|
||||
todo en `scripts/catalogar-visuales.py`: **para añadir películas o personajes
|
||||
nuevos se tocan ahí y ya**.
|
||||
|
||||
Un clip que no encaje en ninguna tabla no rompe nada: sale con el nombre
|
||||
prettificado y agrupado en "Sin catalogar".
|
||||
|
||||
## Transparencia de verdad (canal alfa)
|
||||
|
||||
Hay **dos formas** de que un personaje se vea sobre las visuales, y conviene
|
||||
no confundirlas:
|
||||
|
||||
**1. Recorte por luminancia (lo de siempre).** El clip `_silueta-negro.mp4`
|
||||
tiene fondo negro opaco, y es el Mezclador el que lo hace transparente al
|
||||
vuelo: `src(s0).layer(src(s1).luma(umbral, 0.15))`. Funciona y va en H.264,
|
||||
que es lo que mejor decodifica una Raspberry. Pega: hay un umbral que ajustar,
|
||||
y las zonas oscuras del propio personaje (pelo, ropa negra) se comen con él.
|
||||
|
||||
**2. Canal alfa de verdad (`_silueta-alfa.webm`).** El clip lleva la
|
||||
transparencia dentro, en un WebM con VP9. No hay umbral, no hay bordes
|
||||
comidos: los píxeles del fondo simplemente **no existen**, y debajo se ve la
|
||||
capa que haya. Se genera con:
|
||||
|
||||
```bash
|
||||
.venv/bin/python3 siluetear.py clip.mp4 --modo alfa
|
||||
```
|
||||
|
||||
FOSFENO ya lo compone bien sin tocar nada: el fragment shader del mapper
|
||||
saca `gl_FragColor = vec4(c.rgb, c.a * uAlpha)` y mezcla con
|
||||
`SRC_ALPHA / ONE_MINUS_SRC_ALPHA`, así que una capa con alfa deja ver la capa
|
||||
dibujada antes que ella. **Comprobado**, no supuesto: con una capa de fondo
|
||||
roja y encima un clip alfa, el 48,6 % de la pantalla sale roja por los huecos.
|
||||
|
||||
> Para que se vea a través hace falta que **haya algo debajo**: otra capa del
|
||||
> mapping dibujada antes (MilkDrop, shader, cámara). Si el clip alfa es la
|
||||
> única capa, debajo solo está el negro del compositor.
|
||||
|
||||
### Dos trampas del alfa en WebM
|
||||
|
||||
- **El decodificador VP9 nativo de ffmpeg se come el alfa en silencio**: no
|
||||
falla, devuelve el vídeo entero opaco. Para leerlo hay que pedir
|
||||
`-c:v libvpx-vp9` a mano. Es exactamente por esto que el catalogador mide
|
||||
estos clips con `ffmpeg -c:v libvpx-vp9 ... -vf format=rgba,alphaextract`
|
||||
(y el `format=rgba` no es adorno: sin él `alphaextract` no negocia formato
|
||||
y ffmpeg aborta).
|
||||
- **VP9 no tiene decodificación por hardware en la Raspberry Pi 5**, que sí la
|
||||
tiene para H.264/HEVC. En el portátil va sobrado; en la Pi, con clips de
|
||||
480p cortos debería ir, pero **hay que probarlo antes de un bolo**. Si se
|
||||
atraganta, el camino 1 (negro + luma key) sigue ahí.
|
||||
|
||||
## En el panel
|
||||
|
||||
`server.py` carga el catálogo en `state.meta.visuales` (`{archivo: ficha}`),
|
||||
filtrado contra lo que hay de verdad en disco para que un catálogo viejo no
|
||||
enseñe clips borrados. El panel lo usa en `fillClipSelect()`: las listas de
|
||||
clips salen agrupadas por película con `<optgroup>`, con el título legible, la
|
||||
duración y la marca de calidad (`· poca figura`, `· casi vacio`), y el aviso
|
||||
completo en el `title` de cada opción.
|
||||
Loading…
Add table
Add a link
Reference in a new issue