Três viagens de carro pelos Andes, pelo pampa e pelo litoral do Uruguai: satélite real, o carro na estrada verdadeira, HUD com km, altitude e país, e um diário de bordo com foto e fato de cada lugar.

Cada viagem sai em 9:16 (Reels/Shorts/WhatsApp) e 16:9 (YouTube/TV). Os arquivos aqui são a versão web (30 fps); os masters 60 fps ficam fora do git.
Nada aqui foi montado numa timeline à mão. Uma página HTML lê os dados da viagem e desenha cada quadro; o HyperFrames grava essa página como vídeo.
Estrada do OSRM, balsa e fronteiras do OpenStreetMap, satélite Esri, altitude SRTM, fatos da Wikipedia e fotos do Wikimedia Commons com autor e licença.
Carro, câmera, linha percorrida, pinos, HUD, trilho de altitude e cards saem da mesma função do tempo. Por isso nada sai de sincronia.
A trilha é analisada (BPM e fase): o carro larga quando a batida entra, a balsa atravessa no trecho calmo e o carimbo de fronteira cai no retorno.
São duas metades: scripts Python que preparam os dados (rodam uma vez por viagem) e um index.html que desenha o quadro a partir desses dados, para qualquer instante t.

data/, Python)Cada viagem tem sua pasta data/ com os scripts e o resultado. Os scripts falam direto com as APIs abertas e guardam cache local.
| Script | O que faz | Sai |
|---|---|---|
build_route2.py trip.json | Junta as pernas do OSRM e a linha da balsa (OSM) numa rota só, com quilômetro cumulativo único. Marca onde a balsa começa/termina e onde está a fronteira (polígono OSM admin_level=2), encaixa cada parada no km certo e busca a altitude a cada 5 km no OpenTopoData (SRTM 30 m). Reamostra a rota a cada 0,15 km em coordenadas Web Mercator z14. | route_full.json → assets/route.js |
build_tiles.py | Baixa o satélite Esri em quatro níveis: ov z7 (visão geral), cor z8 (corredor da rota), e para cada parada p10 z10 (±1,2°) e p12 z12 (±0,3°). Cada nível vira um JPG graduado com sua posição no mundo. | assets/map/*.jpg + assets/layers.js |
photos_search.py q.json dir | Procura candidatas no Wikimedia Commons (JPEG, paisagem, ≥ 1400 px) e monta folhas de contato para escolher. | folhas de contato → picks.json |
photos_get.py picks.json | Baixa as escolhidas em 1600 px e grava autor, licença e página de cada uma. | assets/photos/ + photo_credits.json |
fetch_facts.py pages.json | Baixa o texto dos verbetes da Wikipedia (ES/PT/EN). Cada frase dos cards sai dali, com a fonte salva. | data/facts/*.json |
beat.py música.mp3 | Mede BPM e fase da batida e do compasso (fluxo espectral + autocorrelação). É o que diz em que segundo o carro larga e o carimbo cai. | números para o trip.js |
assets/trip.js)É o roteiro do vídeo, escrito à mão. Diz quando o carro está em cada parada; o motor calcula todo o resto.
// 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 em que chega e sai; dwellKm = quantos km cabem na largura do quadro enquanto está parado ali (o zoom da parada). cards têm início e fim (s/e) em segundos; stamps são os carimbos de fronteira.
index.html, JS + GSAP)Uma única timeline GSAP anima um número, t, de 0 até a duração. A cada mudança chama render(), que calcula tudo do zero para aquele t. O HyperFrames avança o tempo quadro a quadro e grava.
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 a saída de uma parada e a chegada na próxima, progress(t) usa um perfil trapezoidal de velocidade: acelera, anda constante, freia. Parado, o km não muda. Como o km é único para a viagem toda, balsa e estrada são o mesmo eixo.
Na parada, o zoom mostra dwellKm de largura. No meio do trecho, abre para ver ~85% dele (curva seno) e olha 20% à frente do carro; na balsa não abre. O vídeo começa na visão geral, mergulha até a largada e, no fim, volta à rota inteira. O zoom é interpolado em escala logarítmica, por isso é suave.
As camadas z7 → z8 → z10 → z12 são empilhadas por detalhe. Uma camada mais nítida só entra quando cobre o quadro inteiro. Assim nunca aparece borda de recorte.
País (troca no fim da balsa e na fronteira), estrada (dos passos do OSRM), altitude (SRTM interpolado) e direção do carro saem do mesmo km. Pinos e rótulos têm anti-colisão e somem quando o zoom afasta.
render(): são elementos com data-start/data-duration (trilhas do HyperFrames), gerados a partir de trip.js. A música e os efeitos (whoosh, carimbo, buzina do navio) também têm horário fixo, casado com o beat.py.trajeto-hua-hum/O primeiro, feito sob medida para 54 km de montanha com dois carros. Scripts próprios (build_route.py, build_map.py, gen_cars.py) e water.py, que repinta os lagos com os polígonos de água do OSM para tirar reflexo do satélite.
volta-defender/, ida-defender/Feito para viagens longas: pernas + balsa + fronteira por configuração (trip.json), satélite em camadas por parada. A ida reaproveitou o motor da volta e custou menos da metade.
O index.html tem dois blocos marcados, /*LAYOUT-CSS-START*/ e /*LAYOUT-JS-START*/ (tamanho do quadro, posição dos cards, fontes). make_16x9.py copia o projeto trocando só esses blocos; os assets entram por link simbólico.
Tudo roda local. As APIs usadas (OSRM, Overpass, OpenTopoData, Esri, Wikipedia, Commons) são abertas e sem chave.
Para pré-visualizar e renderizar. A versão fica fixada no package.json de cada projeto.
npx --yes hyperframes@0.8.72 --helpPara os scripts de dados: numpy e Pillow.
pip install numpy pillowUsado pelo beat.py, pelo render de compartilhar (−14 LUFS) e pela versão ≤ 44 MB.
ffmpeg -versionOs dados das três viagens já estão no repo. Para ver e renderizar, bastam os passos 1, 5 e 6. Para uma viagem nova, copie ida-defender/ e refaça os passos 2 a 4.
O preview do HyperFrames abre o quadro no navegador, com a timeline para arrastar.
git clone https://github.com/inematds/expedicaosul cd expedicaosul/ida-defender npm run dev # = npx hyperframes preview
Salve a rota OSRM de cada perna (osrm_A.json, osrm_B.json), a linha da balsa (ferry.json) e descreva pernas, fronteira e paradas no trip.json. Pernas separadas ancoradas nas pontas da balsa evitam que o OSRM dê a volta por terra.
cd ida-defender/data python3 build_route2.py trip.json # → route_full.json (km único, balsa, fronteira, altitude)
Escolha as fotos pelas folhas de contato e registre em picks.json; liste os verbetes em 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.jsMeça a batida e use os tempos na agenda: largada na entrada do groove, balsa no trecho calmo, carimbo no 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 + versão de compartilhar com áudio normalizado; opcionalmente a versão leve 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 nos logs da sessão do Claude Code (tokens por chamada), em dólar equivalente de API. Quem usa por assinatura não paga esse valor por uso; serve para comparar.
| Vídeo | Tempo do agente | Custo IA (US$) | Créditos Magnific |
|---|---|---|---|
| Hua Hum 9:16 | 55 min | 13,12 | 190 |
| Hua Hum 16:9 | ~5 min | 1,07 | — |
| A volta (9:16 + 16:9) | 38,5 min | 15,18 | 180 |
| A ida (9:16 + 16:9) | 24,4 min | 6,85 | 160 |
| Total | ~2h03 | 36,22 | 530 |
Para quem for reaproveitar o motor.