FOSFENO/docs/visuales.md
hacklab f017246f0e 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>
2026-08-02 14:56:01 +02:00

160 lines
6.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 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 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.