Animaciones de trayecto · HyperFrames

Los viajes reales se convierten en mapa animado

Tres viajes en auto por los Andes, la pampa y la costa del Uruguay: satélite real, el auto en la carretera real, HUD con km, altitud y país, y un diario de a bordo con foto y dato verificado de cada lugar.

Banner Expedição Sul: mapa del sur de Sudamérica con la ruta, la Defender en el ferry y los recursos del motor (ruta OSRM + ferry OSM, satélite en capas, reloj único, diario de a bordo, sincronizado con la música, 9:16 y 16:9)
Los videos

Tres viajes, ocho videos

Cada viaje sale en 9:16 (Reels/Shorts/WhatsApp) y 16:9 (YouTube/TV). Los archivos aquí son la versión web (30 fps); los masters 60 fps quedan fuera del git.

Hua Hum: nuestro plan y nuestra aventura

El plan (SMA ▸ Pirihueico) y lo que pasó: carretera interrumpida en el km 32, el Taos vuelve, la Defender 110 llega al puente del Río Hua Hum y la frontera está cerrada por derrumbes. Mapa general + mapa detallado + 43 fotos del viaje · 2 min 54 s.

Hua Hum

San Martín de los Andes (AR) ▸ Pirihueico (CL). Tucson + Defender · 54,3 km · 1 frontera · 74,4 s.

La vuelta

San Martín de los Andes ▸ Canela (RS). Defender 110 · 2.797 km · 3 países · ferry por el Río de la Plata · 113,6 s.

La ida

Canela ▸ San Martín de los Andes. Defender 110 · 2.882 km · por la costa uruguaya, ferry Colonia ▸ Buenos Aires · 95,7 s.

Qué es

Un motor de ruta, no un editor de video

No se armó nada aquí a mano en una timeline. Una página HTML lee los datos del viaje y dibuja cada cuadro; HyperFrames graba esa página como video.

🛰️ Todo es dato real

Carretera de OSRM, ferry y fronteras de OpenStreetMap, satélite Esri, altitud SRTM, datos de Wikipedia y fotos de Wikimedia Commons con autor y licencia.

⏱️ Un solo reloj

El auto, la cámara, la línea recorrida, los pines, el HUD, el riel de altitud y las tarjetas salen de la misma función del tiempo. Por eso nada sale fuera de sincronía.

🎵 Sincronizado con la música

La pista se analiza (BPM y fase): el auto arranca cuando entra el beat, el ferry atraviesa en el tramo tranquilo y el sello de frontera cae en el retorno.

Cómo funciona el motor

Del link de Google Maps al MP4

Son dos mitades: scripts Python que preparan los datos (se ejecutan una vez por viaje) y un index.html que dibuja el cuadro a partir de esos datos, para cualquier instante t.

Paradas del link→ Ruta OSRM + ferry OSM→ route.js (km único)→ Satélite en capas→ Fotos + hechos→ trip.js (agenda)→ index.html renderiza t→ HyperFrames → MP4

1 · Anatomía de un cuadro

Cuadro 16:9 del video La vuelta en el km 675, cerca del Río Colorado
  1. Mapa: mosaico de satélite; la cámara sigue al auto y cambia el zoom sola.
  2. Línea ámbar: tramo ya recorrido. Punteado: lo que falta.
  3. Vehículo: sprite visto desde arriba (generado en flux2-klein), girado por la dirección de la carretera.
  4. HUD: kilómetro actual / total, altitud (SRTM), bandera del país y nombre de la carretera (RN22, BR-290…).
  5. Riel de altitud: el perfil de todo el viaje, con un cursor avanzando junto a él.
  6. Diario de a bordo: tarjeta con foto, kilómetro y un dato verificado; algunas son tarjetas de datos (gráfico de altitud, contadores).

2 · Preparar los datos (data/, Python)

Cada viaje tiene su carpeta data/ con los scripts y el resultado. Los scripts hablan directo con las APIs abiertas y guardan caché local.

ScriptQué haceSale
build_route2.py trip.jsonUne los tramos de OSRM y la línea del ferry (OSM) en una sola ruta, con kilómetro acumulativo único. Marca dónde empieza/termina el ferry y dónde está la frontera (polígono OSM admin_level=2), encaja cada parada en el km correcto y busca la altitud cada 5 km en OpenTopoData (SRTM 30 m). Re-muestrea la ruta cada 0,15 km en coordenadas Web Mercator z14.route_full.json → assets/route.js
build_tiles.pyDescarga el satélite Esri en cuatro niveles: ov z7 (vista general), cor z8 (corredor de la ruta), y para cada parada p10 z10 (±1,2°) y p12 z12 (±0,3°). Cada nivel se vuelve un JPG graduado con su posición en el mundo.assets/map/*.jpg + assets/layers.js
photos_search.py q.json dirBusca candidatas en Wikimedia Commons (JPEG, paisaje, ≥ 1400 px) y arma hojas de contacto para elegir.hojas de contacto → picks.json
photos_get.py picks.jsonDescarga las elegidas en 1600 px y guarda autor, licencia y la página de cada una.assets/photos/ + photo_credits.json
fetch_facts.py pages.jsonDescarga el texto de los artículos de Wikipedia (ES/PT/EN). Cada frase de las tarjetas sale de ahí, con la fuente guardada.data/facts/*.json
beat.py música.mp3Mide BPM y fase del beat y del compás (flujo espectral + autocorrelación). Es lo que dice en qué segundo el auto arranca y cuándo cae el sello.números para el trip.js

3 · Describir el viaje (assets/trip.js)

Es el guion del video, escrito a mano. Dice cuando el auto está en cada parada; el motor calcula todo lo demás.

// trecho real de volta-defender/assets/trip.js
schedule: [
  { id: "sma",       dep: 9.4,             dwellKm: 42 },  // larga quando o groove entra (9,4 s)
  { id: "bb",        arr: 27.0, dep: 28.2, dwellKm: 42 },  // Bahía Blanca
  { id: "ferry_ba",  arr: 50.6, dep: 56.2, dwellKm: 80 },  // embarca em Buenos Aires
  { id: "ferry_col", arr: 64.8, dep: 66.4, dwellKm: 50 },  // desembarca em Colonia
  ...
],
ferry:  { dep: 56.2, arr: 64.8 },                         // travessia no trecho calmo da trilha
stamps: [{ t: 64.8, s1: "ARGENTINA ▸ URUGUAI", s2: "COLONIA" }, ...],
cards:  [{ id: "sma", s: 7.2, e: 13.2, img: "sma", km: "0", ttl: "San Martín de los Andes", body: "..." }, ...]

arr/dep = segundo en que llega y sale; dwellKm = cuántos km caben en el ancho del cuadro mientras está detenido allí (el zoom de la parada). cards tienen inicio y fin (s/e) en segundos; stamps son los sellos de frontera.

4 · Dibujar el cuadro (index.html, JS + GSAP)

Una sola timeline GSAP anima un número, t, de 0 hasta la duración. Con cada cambio llama a render(), que calcula todo de cero para ese t. HyperFrames avanza el tiempo cuadro a cuadro y graba.

tl.to(st, { t: T.dur, duration: T.dur, ease: "none", onUpdate: render }, 0);

// render(): para o instante t
km     = progress(t)      // onde o carro está (em km)
[x, y] = posAt(km)       // ponto da estrada nesse km
cam    = camera(t)        // centro + escala do mapa
// → posiciona camadas de satélite, linha, pinos, rótulos, carro, HUD, trilho
t→km

El tiempo se vuelve kilómetro

Entre la salida de una parada y la llegada a la siguiente, progress(t) usa un perfil trapezoidal de velocidad: acelera, va constante, frena. Parado, el km no cambia. Como el km es único para todo el viaje, ferry y carretera son el mismo eje.

cam

La cámara se dirige sola

En la parada, el zoom muestra dwellKm de ancho. En medio del tramo, abre para ver ~85% (curva seno) y mira 20% hacia adelante del auto; en el ferry no abre. El video empieza en la vista general, baja hasta la largada y, al final, vuelve a toda la ruta. El zoom se interpola en escala logarítmica, por eso es suave.

z7–12

Satélite que solo aparece entero

Las capas z7 → z8 → z10 → z12 se apilan por detalle. Una capa más nítida solo entra cuando cubre el cuadro entero. Así nunca aparece el borde del recorte.

HUD

Visor derivado del km

País (cambia al final del ferry y en la frontera), carretera (de los pasos de OSRM), altitud (SRTM interpolado) y dirección del auto salen del mismo km. Los pines y las etiquetas tienen anti-colisión y desaparecen cuando el zoom se aleja.

Tarjetas y sonidos no están en el render(): son elementos con data-start/data-duration (pistas de HyperFrames), generados a partir de trip.js. La música y los efectos (whoosh, sello, bocina del barco) también tienen horario fijo, sincronizados con el beat.py.

5 · Dos versiones del motor

v1 · trajeto-hua-hum/

El primero, hecho a medida para 54 km de montaña con dos autos. Scripts propios (build_route.py, build_map.py, gen_cars.py) y water.py, que repinta los lagos con los polígonos de agua de OSM para sacar el reflejo del satélite.

v2 genérico · volta-defender/, ida-defender/

Hecho para viajes largos: tramos + ferry + frontera por configuración (trip.json), satélite en capas por parada. La ida reutilizó el motor de la vuelta y costó menos de la mitad.

6 · 9:16 y 16:9 con el mismo código

El index.html tiene dos bloques marcados, /*LAYOUT-CSS-START*/ y /*LAYOUT-JS-START*/ (tamaño del cuadro, posición de las tarjetas, fuentes). make_16x9.py copia el proyecto cambiando solo esos bloques; los assets entran por link simbólico.

Requisitos previos

Lo que necesitas en la máquina

Todo corre local. Las APIs usadas (OSRM, Overpass, OpenTopoData, Esri, Wikipedia, Commons) son abiertas y sin clave.

Node + HyperFrames

Para previsualizar y renderizar. La versión queda fija en package.json de cada proyecto.

npx --yes hyperframes@0.8.72 --help

Python 3

Para los scripts de datos: numpy y Pillow.

pip install numpy pillow

ffmpeg

Usado por beat.py, por el render de compartir (−14 LUFS) y por la versión ≤ 44 MB.

ffmpeg -version
Guía de uso · paso a paso

Ejecutar un viaje ya listo o hacer uno nuevo

Los datos de los tres viajes ya están en el repo. Para ver y renderizar, bastan los pasos 1, 5 y 6. Para un viaje nuevo, copia ida-defender/ y rehace los pasos 2 a 4.

1

Clonar y abrir un viaje

El preview de HyperFrames abre el cuadro en el navegador, con la timeline para arrastrar.

git clone https://github.com/inematds/expedicaosul
cd expedicaosul/ida-defender
npm run dev      # = npx hyperframes preview
2

Montar la ruta

Guarda la ruta OSRM de cada tramo (osrm_A.json, osrm_B.json), la línea del ferry (ferry.json) y describe las tramos, la frontera y las paradas en trip.json. Tramos separadas ancladas en las puntas del ferry evitan que el OSRM dé la vuelta por tierra.

cd ida-defender/data
python3 build_route2.py trip.json   # → route_full.json (km único, balsa, fronteira, altitude)
3

Satélite, fotos y hechos

Elige las fotos por las hojas de contacto y regístralas en picks.json; lista las entradas en pages.json.

python3 build_tiles.py route_full.json ../assets   # satélite z7/z8/z10/z12
python3 photos_search.py q_ida.json photos_cand  # buscas por parada → candidatas + folhas de contato
python3 photos_get.py picks.json ../assets       # fotos + créditos
python3 fetch_facts.py pages.json                # textos da Wikipedia
4

Sincronizar con la música y escribir el trip.js

Mide el beat y usa los tiempos en la agenda: largada en la entrada del groove, ferry en el tramo tranquilo, sello de frontera en el retorno.

python3 data/beat.py assets/audio/music.mp3 12 55
# → bpm, beat, beat phase, bar phase  → preencher schedule / ferry / stamps / cards
5

Revisar y generar el 16:9

npx hyperframes check                              # sobreposição, texto fora do quadro
python3 make_16x9.py ida-defender ida-defender-16x9
6

Renderizar

Master 60 fps + versión de compartir con audio normalizado; opcionalmente la versión ligera para Telegram/web.

./render_share.sh ida-defender ida-defender-9x16     # renders/…-60fps.mp4 + …mp4 (−14 LUFS)
./tg_encode.sh ida-defender/renders/ida-defender-9x16.mp4 videos/ida-defender-9x16.mp4   # ≤ 44 MB
Costos medidos

¿Cuánto costó hacerlo?

Medido en los logs de la sesión de Claude Code (tokens por llamada), en dólar equivalente de API. Quien usa por suscripción no paga este valor por uso; sirve para comparar.

VideoTiempo del agenteCosto IA (US$)Créditos Magnific
Hua Hum 9:1655 min13,12190
Hua Hum 16:9~5 min1,07—
La vuelta (9:16 + 16:9)38,5 min15,18180
La ida (9:16 + 16:9)24,4 min6,85160
Total~2h0336,22530
98–99,5% de la entrada vino del cache. La vuelta pagó la construcción del motor genérico; la ida solo aprovechó. Imagen (flux2-klein) y renders (HyperFrames) corrieron local, sin costo. Detalles en README.
Estado

Lo que ya está listo y lo que todavía es manual

Para quien vaya a reutilizar el motor.

Listo
3 viajes, 8 videosHua Hum (motor v1), la vuelta y la ida (motor v2 genérico) y “nuestro plan × nuestra aventura” (3 cuadros, fotos del viaje), en 9:16 y 16:9 — este último también en inglés y español.
Manual
Descargar las tramos OSRM y escribir el trip.jsNo hay script que lea el link de Google Maps; las tramos y la agenda por parada se montan a mano (hoy, por el agente).
Manual
Elegir fotosEl script busca y arma hojas de contacto; la elección, verificada por lugar, se va a picks.json.
Fuera de git
Masters 60 fps y cache de tilesLos masters (300+ MB) y los caches de satélite/fotos no están en el repositorio; el repo lleva la versión web.