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.

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

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.
| Script | Qué hace | Sale |
|---|---|---|
build_route2.py trip.json | Une 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.py | Descarga 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 dir | Busca candidatas en Wikimedia Commons (JPEG, paisaje, ≥ 1400 px) y arma hojas de contacto para elegir. | hojas de contacto → picks.json |
photos_get.py picks.json | Descarga 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.json | Descarga 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.mp3 | Mide 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 |
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.
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
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.
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.
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.
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.
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.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.
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.
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.
Todo corre local. Las APIs usadas (OSRM, Overpass, OpenTopoData, Esri, Wikipedia, Commons) son abiertas y sin clave.
Para previsualizar y renderizar. La versión queda fija en package.json de cada proyecto.
npx --yes hyperframes@0.8.72 --helpPara los scripts de datos: numpy y Pillow.
pip install numpy pillowUsado por beat.py, por el render de compartir (−14 LUFS) y por la versión ≤ 44 MB.
ffmpeg -versionLos 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.
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
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)
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
trip.jsMide 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
npx hyperframes check # sobreposição, texto fora do quadro python3 make_16x9.py ida-defender ida-defender-16x9
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
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.
| Video | Tiempo del agente | Costo IA (US$) | Créditos Magnific |
|---|---|---|---|
| Hua Hum 9:16 | 55 min | 13,12 | 190 |
| Hua Hum 16:9 | ~5 min | 1,07 | — |
| La vuelta (9:16 + 16:9) | 38,5 min | 15,18 | 180 |
| La ida (9:16 + 16:9) | 24,4 min | 6,85 | 160 |
| Total | ~2h03 | 36,22 | 530 |
Para quien vaya a reutilizar el motor.