Animações de trajeto · HyperFrames

Viagens reais viram mapa animado

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.

Banner Expedição Sul: mapa do sul da América do Sul com a rota, a Defender na balsa e os recursos do motor (rota OSRM + balsa OSM, satélite em camadas, relógio único, diário de bordo, casado com a música, 9:16 e 16:9)
Os vídeos

Três viagens, oito vídeos

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.

Hua Hum: nosso plano e nossa aventura

O plano (SMA ▸ Pirihueico) e o que aconteceu: estrada interrompida no km 32, o Taos volta, a Defender 110 chega à ponte do Río Hua Hum e a fronteira está fechada por desmoronamentos. Mapa geral + mapa detalhado + 43 fotos da viagem · 2 min 54 s.

Hua Hum

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

A volta

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

A ida

Canela ▸ San Martín de los Andes. Defender 110 · 2.882 km · pelo litoral uruguaio, balsa Colonia ▸ Buenos Aires · 95,7 s.

O que é

Um motor de rota, não um editor de vídeo

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.

🛰️ Tudo é dado real

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.

⏱️ Um relógio só

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.

🎵 Casado com a música

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.

Como funciona o motor

Do link do Google Maps ao MP4

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.

Paradas do link→ Rota OSRM + balsa OSM→ route.js (km único)→ Satélite em camadas→ Fotos + fatos→ trip.js (agenda)→ index.html renderiza t→ HyperFrames → MP4

1 · Anatomia de um quadro

Quadro 16:9 do vídeo A volta no km 675, perto do Río Colorado
  1. Mapa: mosaico de satélite; a câmera segue o carro e muda o zoom sozinha.
  2. Linha âmbar: trecho já percorrido. Pontilhado: o que falta.
  3. Veículo: sprite visto de cima (gerado no flux2-klein), girado pela direção da estrada.
  4. HUD: quilômetro atual / total, altitude (SRTM), bandeira do país e nome da estrada (RN22, BR-290…).
  5. Trilho de altitude: o perfil da viagem inteira, com um cursor andando junto.
  6. Diário de bordo: card com foto, quilômetro e um fato verificado; alguns são cards de dados (gráfico de altitude, contadores).

2 · Preparar os dados (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.

ScriptO que fazSai
build_route2.py trip.jsonJunta 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.pyBaixa 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 dirProcura candidatas no Wikimedia Commons (JPEG, paisagem, ≥ 1400 px) e monta folhas de contato para escolher.folhas de contato → picks.json
photos_get.py picks.jsonBaixa 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.jsonBaixa 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.mp3Mede 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

3 · Descrever a viagem (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.

4 · Desenhar o quadro (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
t→km

Tempo vira quilômetro

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.

cam

A câmera se dirige sozinha

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.

z7–12

Satélite que só aparece inteiro

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.

HUD

Mostrador derivado do km

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.

Cards e sons não estão no 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.

5 · Duas versões do motor

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

v2 genérico · 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.

6 · 9:16 e 16:9 com o mesmo código

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.

Pré-requisitos

O que precisa na máquina

Tudo roda local. As APIs usadas (OSRM, Overpass, OpenTopoData, Esri, Wikipedia, Commons) são abertas e sem chave.

Node + HyperFrames

Para pré-visualizar e renderizar. A versão fica fixada no package.json de cada projeto.

npx --yes hyperframes@0.8.72 --help

Python 3

Para os scripts de dados: numpy e Pillow.

pip install numpy pillow

ffmpeg

Usado pelo beat.py, pelo render de compartilhar (−14 LUFS) e pela versão ≤ 44 MB.

ffmpeg -version
Guia de uso · passo a passo

Rodar uma viagem pronta ou fazer uma nova

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

1

Clonar e abrir uma viagem

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
2

Montar a rota

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)
3

Satélite, fotos e fatos

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
4

Casar com a música e escrever o trip.js

Meç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
5

Checar e gerar o 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 + 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
Custos medidos

Quanto custou fazer

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ídeoTempo do agenteCusto IA (US$)Créditos Magnific
Hua Hum 9:1655 min13,12190
Hua Hum 16:9~5 min1,07—
A volta (9:16 + 16:9)38,5 min15,18180
A ida (9:16 + 16:9)24,4 min6,85160
Total~2h0336,22530
98–99,5% da entrada veio do cache. A volta pagou a construção do motor genérico; a ida só reaproveitou. Imagem (flux2-klein) e renders (HyperFrames) rodaram local, sem custo. Detalhes no README.
Estado

O que está pronto e o que ainda é manual

Para quem for reaproveitar o motor.

Pronto
3 viagens, 8 vídeosHua Hum (motor v1), a volta e a ida (motor v2 genérico) e “nosso plano × nossa aventura” (3 quadros, fotos da viagem), em 9:16 e 16:9 — este último também em inglês e espanhol.
Manual
Baixar as pernas OSRM e escrever o trip.jsNão há script que leia o link do Google Maps; as pernas e a agenda por parada são montadas à mão (hoje, pelo agente).
Manual
Escolher fotosO script busca e monta folhas de contato; a escolha, conferida por local, vai para o picks.json.
Fora do git
Masters 60 fps e cache de tilesOs masters (300+ MB) e os caches de satélite/fotos não estão no repositório; o repo leva a versão web.